Repository: sigmavirus24/github3.py

Issue:
No way to unassign an issue
There seems to be an assign function, but not an unassign method for Issue

<bountysource-plugin>

---
Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/46179344-no-way-to-unassign-an-issue?utm_campaign=plugin&utm_content=tracker%2F183477&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F183477&utm_medium=issues&utm_source=github).
</bountysource-plugin>

Discussion:
sigmavirus24: You're right. This method, at this point in time is not going to be correct or conducive to the right behaviour for the future of the library. Here's what I think we should do:

1. Deprecate `Issue.assign`. It only accepts 1 user and that is outdated and likely going to be confusing going forward.

1. Add `add_assignees` (or something along those lines) that does the right thing (accepts a list of usernames/User objects) and calls `edit` appropriately.

1. Add `remove_assignees` which is the inverse of `add_assignees`.

Does that make sense?
jacquerie: > Does that make sense?

Absolutely! I just pushed three commits that implement the three steps that you described.

Patch:
diff --git a/src/github3/issues/issue.py b/src/github3/issues/issue.py
index 15b7632a..cdf673fc 100644
--- a/src/github3/issues/issue.py
+++ b/src/github3/issues/issue.py
@@ -2,6 +2,7 @@
 """Module containing the Issue logic."""
 from __future__ import unicode_literals
 
+import warnings
 from json import dumps
 
 from uritemplate import URITemplate
@@ -67,6 +68,27 @@ class _Issue(models.GitHubCore):
             n=self.number, class_name=self.class_name,
         )
 
+    @requires_auth
+    def add_assignees(self, users):
+        """Assign ``users`` to this issue.
+
+        This is a shortcut for :meth:`~github3.issues.issue.Issue.edit`.
+
+        :param users:
+            users or usernames to assign this issue to
+        :type users:
+            list of :class:`~github3.users.User`
+        :type users:
+            list of str
+        :returns:
+            True if successful, False otherwise
+        :rtype:
+            bool
+        """
+        usernames = {getattr(user, 'login', user) for user in users}
+        assignees = list({a.login for a in self.assignees} | usernames)
+        return self.edit(assignees=assignees)
+
     @requires_auth
     def add_labels(self, *args):
         """Add labels to this issue.
@@ -86,6 +108,10 @@ class _Issue(models.GitHubCore):
     def assign(self, username):
         """Assign user ``username`` to this issue.
 
+        .. deprecated:: 1.2.0
+
+            Use :meth:`github3.issues.issue.Issue.add_assignees` instead.
+
         This is a short cut for :meth:`~github3.issues.issue.Issue.edit`.
 
         :param str username:
@@ -95,6 +121,9 @@ class _Issue(models.GitHubCore):
         :rtype:
             bool
         """
+        warnings.warn(
+            'This method is deprecated. Please use ``add_assignees`` '
+            'instead.', DeprecationWarning, stacklevel=2)
         if not username:
             return False
         number = self.milestone.number if self.milestone else None
@@ -300,6 +329,27 @@ class _Issue(models.GitHubCore):
             json = self._json(self._get(pull_request_url), 200)
         return self._instance_or_null(pulls.PullRequest, json)
 
+    @requires_auth
+    def remove_assignees(self, users):
+        """Unassign ``users`` from this issue.
+
+        This is a shortcut for :meth:`~github3.issues.issue.Issue.edit`.
+
+        :param users:
+            users or usernames to unassign this issue from
+        :type users:
+            list of :class:`~github3.users.User`
+        :type users:
+            list of str
+        :returns:
+            True if successful, False otherwise
+        :rtype:
+            bool
+        """
+        usernames = {getattr(user, 'login', user) for user in users}
+        assignees = list({a.login for a in self.assignees} - usernames)
+        return self.edit(assignees=assignees)
+
     @requires_auth
     def remove_label(self, name):
         """Remove label ``name`` from this issue.


Test patch:
diff --git a/tests/cassettes/Issue_add_assignees.json b/tests/cassettes/Issue_add_assignees.json
new file mode 100644
index 00000000..418ba17b
--- /dev/null
+++ b/tests/cassettes/Issue_add_assignees.json
@@ -0,0 +1,1 @@
+{"http_interactions": [{"request": {"body": {"string": "", "encoding": "utf-8"}, "headers": {"Accept-Encoding": ["gzip, deflate"], "Accept": ["application/vnd.github.v3.full+json"], "User-Agent": ["github3.py/1.1.0"], "Accept-Charset": ["utf-8"], "Connection": ["keep-alive"], "Content-Type": ["application/json"], "Authorization": ["token <AUTH_TOKEN>"]}, "method": "GET", "uri": "https://api.github.com/repos/sigmavirus24/github3.py/issues/711"}, "response": {"body": {"string": "", "base64_string": "H4sIAAAAAAAAA8VW/W/iNhj+V7xIm7aqISSkcGSBatPupP1Au9Poelo5IScx4GtiR/6Apaj/+147gQNEez2u0yQiiP2+j5/36zFrR4vciZyFUqWMPA+XtDWnaqGTVsoLT5CSS0/SeYGXVGgZhF6922mVlUel1ER6Pd93zh1rShUX1fR0SMDJcUJy+Q0YO7S8GmztMVyQRwCHmArC1GvBb+AAmSxfEbcGA9SFKvKDVOxU50V1oZkTBZ2Li34Y9v1zh/GMTM2aM/rtpvvnh6uH7PamGj3c+Nfj9+3r8dsBHMt0kRDhRFDZc0dRlROwv+JohSukONIMSzibIcyQ7QFw0dI4rJ2czykD64IQVWA21/e0wrBvjux1g/ZFp7NP4n33rw9XefopDUaffvFHDyNDAC+xwuKwCeyibDcNak5MOVOQd9ur2mvwL5eDEDDmokGx0RqKz3W6QZPeIevn839oPeN5zleAc0h8f6iOHOVtXYFn/Zuy+akw4Lr2uFoQyCCEZRp/TuWXuv4YLeu2hpmXCprGAEkogSDZ11NrHIHYigGntdUWi6gTmQpaKsrZCZnbcwc4LuaY0Qd8Ihy4S0Cxavb1QVo3cH+JGhzLd+239kpBlzitTHoESQldQsZPxTwAAEhVlWagb8zIQv6pIlOcFWZsZziX5HEjwk5099FWXBlzXhIG5jlP7wlMlDWFSbVKQMCA6Tz//C5r54LmRCrOtvtbyYwCkGNBADubYgX4Qdvvue2u63fG/kUU9KLQ/xvO02W2Z/PGbcMnGAedqN2P/L6xSXMuG5iGhVYLLqZAjqfUtoIRsOurt2Cc8KyamsGGpbgcjmFMCJKEFNJoW0KMqjX6NtMsNc7nKNEKMa7M3lb9CgKHZGjGBfrdqGDslcMJm7B4IeAboG8xUxYTp/dILais1fISxVIJzubDGKOFILPBZHv/rlarVsI1U5XkWqTEKltzz4Zdv9fvhKHLuAtK7Crubri4mLnW6lKrYpriosRQlUGZa1DjH+D1Z7teq+VACSBExPfBO/9NJ+z1tgYFyaguBvV529WayaC+eCYOEiQHxozXOjVxhn9wCZlBNW/E4VJQ38UeHsZeEye6haymKSlVbUSJREuK0Qvj/1+C+nWnCiaYVpzYskKRN02kyD+mcV+vg0z3TNiTbXMsz0+ldpd+qyH8ulwFcIUn3m1Xt+64odkwj+u65uvJiM7O7o4F9fHHzf/R/2Qenp0Fs7k/B2ZlbwZ+Ojt7Ku13u3n/QhhH2/qbubVsUbxjVfmslElV6/Xjv5qtd9IADAAA", "encoding": "utf-8"}, "headers": {"X-XSS-Protection": ["1; mode=block"], "Content-Security-Policy": ["default-src 'none'"], "Access-Control-Expose-Headers": ["ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"], "Transfer-Encoding": ["chunked"], "Last-Modified": ["Thu, 02 Aug 2018 23:09:19 GMT"], "Access-Control-Allow-Origin": ["*"], "X-Frame-Options": ["deny"], "Status": ["200 OK"], "X-GitHub-Request-Id": ["A2E6:3FAE:2C29574:52316EC:5B6404DA"], "ETag": ["W/\"5b4dd8dd9fd32bdbfc73353b8813f599\""], "Date": ["Fri, 03 Aug 2018 07:31:38 GMT"], "X-RateLimit-Remaining": ["4999"], "Strict-Transport-Security": ["max-age=31536000; includeSubdomains; preload"], "Server": ["GitHub.com"], "X-OAuth-Scopes": ["public_repo, read:user"], "X-GitHub-Media-Type": ["github.v3; param=full; format=json"], "X-Content-Type-Options": ["nosniff"], "Content-Encoding": ["gzip"], "X-Runtime-rack": ["0.104248"], "Vary": ["Accept, Authorization, Cookie, X-GitHub-OTP"], "X-RateLimit-Limit": ["5000"], "Cache-Control": ["private, max-age=60, s-maxage=60"], "Referrer-Policy": ["origin-when-cross-origin, strict-origin-when-cross-origin"], "Content-Type": ["application/json; charset=utf-8"], "X-Accepted-OAuth-Scopes": ["repo"], "X-RateLimit-Reset": ["1533285098"]}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/repos/sigmavirus24/github3.py/issues/711"}, "recorded_at": "2018-08-03T07:31:38"}, {"request": {"body": {"string": "{\"assignees\": [\"jacquerie\"]}", "encoding": "utf-8"}, "headers": {"Content-Length": ["28"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["application/vnd.github.v3.full+json"], "User-Agent": ["github3.py/1.1.0"], "Accept-Charset": ["utf-8"], "Connection": ["keep-alive"], "Content-Type": ["application/json"], "Authorization": ["token <AUTH_TOKEN>"]}, "method": "PATCH", "uri": "https://api.github.com/repos/sigmavirus24/github3.py/issues/711"}, "response": {"body": {"string": "", "base64_string": "H4sIAAAAAAAAA+1YbW/iRhD+K66lVm0UY4wdSCgQXdWrdB8gdyppTg0ntLYX2MTe9e0L1EH57zdr82IsSMJL1Q89CQSsZ559dnae2R3mpuKR2TQnUiaiadsoIZUxkRPlVwIW2xwnTNiCjGM0JVyJmmfnT91KktpECIWF3XAc89zMTIlkPB0eDgk4EfJxJI7AKNCyc7C5TVGMnwEc1hRjKk8Fv4QDZDw9IW4OBqgTGUelUBR25037QkKzWXMvLq4878o5NykL8VCPmd3fb+t/fu49hXe3affp1rnpf6re9N+3YVqqYh9zswk7e25KIiMM9j1mzFBqSGYoigTMTQ1EjSwHwEUJ7TA3IzYmFKxjjGWM6Fg9khTBcz1lo16rXrjuJolP9b8+96LgIah1H9453aeuJoCmSCJeToJsUFQXCapnDBiVEPcsV5W9wL+etj3AGPMFSrZaTfGlTNdowi6zfjn+ZesRiyI2A5wy8U1RbZnKXrkCz/w7oeNDYcB1bjM5wRBBWJZO/DERr2X9NlqZ2xw0LyQkjQYSsAUch/tTWzgCsRkFTvOstmSIyhcBJ4kkjB4QuQ13gGN8jCh5QgfCgbsAlKya7b/IzA3c31INtsU795vbCSdTFKQ6PBwHmEwh4odilgAAUqaJFvStlizEn0g8RGGsZTtCkcDPyyJsNu+/ZDsutTlLMAXziAWPGBSVmYJSs0qAwWCt/QcUfFWYE10XtPjcS6d2Wd0u+67X7X/wuu/ar8m+tlP2OfwRqi/yfVnxRcu91L5yPFzpZYhjVL7GOkrha5jTqbuAWSwMhyh7DbWvqtee+yt67XsaNRe5bJSCNyl5qU8BYv6u0K2X3LwQl+UFxzgEeK+zuAzxXaG7O4ty0P+vCoUDNiYRFpJROEOpiqJ1lwIXd/jBMZy/4RBJCGat6jSsat1y3L5z0aw1mp7zN2SpSsINm0urCi+3X200XafpXmmbIGJiAZPPgpScMD6EAsECkl2XYILeTe89GPssTIf6KIShVtLpw1USGwLjWOj7v4/1zX/RA4wUDbTzueEraVAm9bNVhxBjmCQ0RowbH3Sn0LKTzoAOaGvC4ROg7xCVGSYKHg05ISLvKK6NlpCc0XGnhYwJx6P2YNWjzmazis8Ulalgigc4u/0velGv7jSuXM+zKLOgW7Eks5ZcLEStzOpayXgYoDhBcHNpJ5GCjuUn+PlrNp53FG3JgRDmP9b+cC5dr9FYGcQ4JCpu5/OtRnMm7bw5G5gGxxEwpiyvHwOz85EJiIyR8zYYNE7yh5aNOi17sU7jDqIaBDiRuRHBwpgSZLxx/f/Jon4r7IJeTKXlZ9sKm7xMIon/0Yl7ugzS2TOgO9NmW5x3hbZIv7IgfFquHLjCu1VMVyvPuI5+oN+WZemPnSs6O7vftqgvPy//s/lX9PCiFvTDTR3okQ0N/HJ2tivs98W4v7KMrWl9NLdKtin2tl1ZV0o/zevx8zchJQr2JBMAAA==", "encoding": "utf-8"}, "headers": {"X-XSS-Protection": ["1; mode=block"], "Content-Security-Policy": ["default-src 'none'"], "Access-Control-Expose-Headers": ["ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"], "Transfer-Encoding": ["chunked"], "Access-Control-Allow-Origin": ["*"], "X-Frame-Options": ["deny"], "Status": ["200 OK"], "X-GitHub-Request-Id": ["A2E6:3FAE:2C295A6:5231732:5B6404DA"], "ETag": ["W/\"b4505c5c6261cbcfd05fc36ec2685139\""], "Date": ["Fri, 03 Aug 2018 07:31:39 GMT"], "X-RateLimit-Remaining": ["4998"], "Strict-Transport-Security": ["max-age=31536000; includeSubdomains; preload"], "Server": ["GitHub.com"], "X-OAuth-Scopes": ["public_repo, read:user"], "X-GitHub-Media-Type": ["github.v3; param=full; format=json"], "X-Content-Type-Options": ["nosniff"], "Content-Encoding": ["gzip"], "X-Runtime-rack": ["0.706423"], "Vary": ["Accept, Authorization, Cookie, X-GitHub-OTP"], "X-RateLimit-Limit": ["5000"], "Cache-Control": ["private, max-age=60, s-maxage=60"], "Referrer-Policy": ["origin-when-cross-origin, strict-origin-when-cross-origin"], "Content-Type": ["application/json; charset=utf-8"], "X-Accepted-OAuth-Scopes": [""], "X-RateLimit-Reset": ["1533285098"]}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/repos/sigmavirus24/github3.py/issues/711"}, "recorded_at": "2018-08-03T07:31:39"}], "recorded_with": "betamax/0.8.1"}
\ No newline at end of file
diff --git a/tests/cassettes/Issue_remove_assignees.json b/tests/cassettes/Issue_remove_assignees.json
new file mode 100644
index 00000000..2a9ff031
--- /dev/null
+++ b/tests/cassettes/Issue_remove_assignees.json
@@ -0,0 +1,1 @@
+{"http_interactions": [{"request": {"body": {"string": "", "encoding": "utf-8"}, "headers": {"Accept-Encoding": ["gzip, deflate"], "Accept": ["application/vnd.github.v3.full+json"], "User-Agent": ["github3.py/1.1.0"], "Accept-Charset": ["utf-8"], "Connection": ["keep-alive"], "Content-Type": ["application/json"], "Authorization": ["token <AUTH_TOKEN>"]}, "method": "GET", "uri": "https://api.github.com/repos/sigmavirus24/github3.py/issues/711"}, "response": {"body": {"string": "", "base64_string": "H4sIAAAAAAAAA+1YbW/iRhD+K66lVm0UY4wdSCgQXdWrdB8gdyppTg0ntLYX2MTe9e0L1EH57zdr82IsSMJL1Q89CQSsZ559dnae2R3mpuKR2TQnUiaiadsoIZUxkRPlVwIW2xwnTNiCjGM0JVyJmmfnT91KktpECIWF3XAc89zMTIlkPB0eDgk4EfJxJI7AKNCyc7C5TVGMnwEc1hRjKk8Fv4QDZDw9IW4OBqgTGUelUBR25037QkKzWXMvLq4878o5NykL8VCPmd3fb+t/fu49hXe3affp1rnpf6re9N+3YVqqYh9zswk7e25KIiMM9j1mzFBqSGYoigTMTQ1EjSwHwEUJ7TA3IzYmFKxjjGWM6Fg9khTBcz1lo16rXrjuJolP9b8+96LgIah1H9453aeuJoCmSCJeToJsUFQXCapnDBiVEPcsV5W9wL+etj3AGPMFSrZaTfGlTNdowi6zfjn+ZesRiyI2A5wy8U1RbZnKXrkCz/w7oeNDYcB1bjM5wRBBWJZO/DERr2X9NlqZ2xw0LyQkjQYSsAUch/tTWzgCsRkFTvOstmSIyhcBJ4kkjB4QuQ13gGN8jCh5QgfCgbsAlKya7b/IzA3c31INtsU795vbCSdTFKQ6PBwHmEwh4odilgAAUqaJFvStlizEn0g8RGGsZTtCkcDPyyJsNu+/ZDsutTlLMAXziAWPGBSVmYJSs0qAwWCt/QcUfFWYE10XtPjcS6d2Wd0u+67X7X/wuu/ar8m+tlP2OfwRqi/yfVnxRcu91L5yPFzpZYhjVL7GOkrha5jTqbuAWSwMhyh7DbWvqtee+yt67XsaNRe5bJSCNyl5qU8BYv6u0K2X3LwQl+UFxzgEeK+zuAzxXaG7O4ty0P+vCoUDNiYRFpJROEOpiqJ1lwIXd/jBMZy/4RBJCGat6jSsat1y3L5z0aw1mp7zN2SpSsINm0urCi+3X200XafpXmmbIGJiAZPPgpScMD6EAsECkl2XYILeTe89GPssTIf6KIShVtLpw1USGwLjWOj7v4/1zX/RA4wUDbTzueEraVAm9bNVhxBjmCQ0RowbH3Sn0LKTzoAOaGvC4ROg7xCVGSYKHg05ISLvKK6NlpCc0XGnhYwJx6P2YNWjzmazis8Ulalgigc4u/0velGv7jSuXM+zKLOgW7Eks5ZcLEStzOpayXgYoDhBcHNpJ5GCjuUn+PlrNp53FG3JgRDmP9b+cC5dr9FYGcQ4JCpu5/OtRnMm7bw5G5gGxxEwpiyvHwOz85EJiIyR8zYYNE7yh5aNOi17sU7jDqIaBDiRuRHBwpgSZLxx/f/Jon4r7IJeTKXlZ9sKm7xMIon/0Yl7ugzS2TOgO9NmW5x3hbZIv7IgfFquHLjCu1VMVyvPuI5+oN+WZemPnSs6O7vftqgvPy//s/lX9PCiFvTDTR3okQ0N/HJ2tivs98W4v7KMrWl9NLdKtin2tl1ZV0o/zevx8zchJQr2JBMAAA==", "encoding": "utf-8"}, "headers": {"X-XSS-Protection": ["1; mode=block"], "Content-Security-Policy": ["default-src 'none'"], "Access-Control-Expose-Headers": ["ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"], "Transfer-Encoding": ["chunked"], "Last-Modified": ["Fri, 03 Aug 2018 07:31:39 GMT"], "Access-Control-Allow-Origin": ["*"], "X-Frame-Options": ["deny"], "Status": ["200 OK"], "X-GitHub-Request-Id": ["95AE:3FAE:2C780A7:52C3D04:5B640BE5"], "ETag": ["W/\"b4505c5c6261cbcfd05fc36ec2685139\""], "Date": ["Fri, 03 Aug 2018 08:01:41 GMT"], "X-RateLimit-Remaining": ["4997"], "Strict-Transport-Security": ["max-age=31536000; includeSubdomains; preload"], "Server": ["GitHub.com"], "X-OAuth-Scopes": ["public_repo, read:user"], "X-GitHub-Media-Type": ["github.v3; param=full; format=json"], "X-Content-Type-Options": ["nosniff"], "Content-Encoding": ["gzip"], "X-Runtime-rack": ["0.080789"], "Vary": ["Accept, Authorization, Cookie, X-GitHub-OTP"], "X-RateLimit-Limit": ["5000"], "Cache-Control": ["private, max-age=60, s-maxage=60"], "Referrer-Policy": ["origin-when-cross-origin, strict-origin-when-cross-origin"], "Content-Type": ["application/json; charset=utf-8"], "X-Accepted-OAuth-Scopes": ["repo"], "X-RateLimit-Reset": ["1533285098"]}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/repos/sigmavirus24/github3.py/issues/711"}, "recorded_at": "2018-08-03T08:01:41"}, {"request": {"body": {"string": "{\"assignees\": []}", "encoding": "utf-8"}, "headers": {"Content-Length": ["17"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["application/vnd.github.v3.full+json"], "User-Agent": ["github3.py/1.1.0"], "Accept-Charset": ["utf-8"], "Connection": ["keep-alive"], "Content-Type": ["application/json"], "Authorization": ["token <AUTH_TOKEN>"]}, "method": "PATCH", "uri": "https://api.github.com/repos/sigmavirus24/github3.py/issues/711"}, "response": {"body": {"string": "", "base64_string": "H4sIAAAAAAAAA8VW/W/iNhj+V7xIm7aqIQRSaHOBatNu0n6g3Wn0erpyQk5igq+JHfkDlqL+73vtBAaI9npcT5OIIPb7Pn7er8esHC1yJ3TmSpUy9Dxc0lZG1VzHrYQXniAll56kWYEXVGjZCbx6t9sqK49KqYn0+r7vnDrWlCouqunxkICT45jk8hswtmh5NdjKY7ggjwAOMRWEqdeCX8MBMlm8Im4NBqhzVeR7qdiqzovqQlMn7HTPzi6C4MI/dRhPydSsOaPfb3p/f7h6SG9vqtHDjX89fte+Hr8dwLFMFzERTgiVPXUUVTkB+yuOlrhCiiPNsISzGcIM2R4AFy2Nw8rJeUYZWBeEqAKzTN/TCsO+ObLf67TPut1dEu967z9c5cnnpDP6/Ks/ehgZAniBFRb7TWAXZbtpUHNiwpmCvNte1V6Df7kYBICRiQbFRmsoPtfpBk16+6yfz/++9YznOV8Czj7x3aE6cJS3cQWe9W/KsmNhwHXlcTUnkEEIyzR+RuWXuv4QLeu2gpmXCprGAEkogSDp11NrHIHYkgGnldUWi6hjmQhaKsrZEZnbcQc4LjLM6AM+Eg7cJaBYNfv6IK0buL9EDQ7lu/ZbeaWgC5xUJj2CJIQuIOPHYu4BAKSqSjPQN2ZkIf9UkSlOCzO2M5xL8rgWYSe8+2Qrrow5LwkD85wn9wQmyprCpFolIGDAdJ7/9y5r54LmRCrONvsbyQw7IMeCAHY6xQrwO22/77Z7rt8d+2dhpx8G/kc4T5fpjs2524ZPd9w+D9t+Y5PkXDYwDQut5lxMgRxPqG0FI2DXV28BMOZpNTWDDUtRORzDmBAkCSmk0baYGFVr9G2mWWKcT1GsFWJcmb2N+hUEDknRjAv0p1HByCuHEzZh0VzAN0DfYqYsJk7ukZpTWavlJYqkEpxlwwijuSCzwWRz/y6Xy1bMNVOV5FokxCpbc88GPb9/0Q0Cl3EXlNhV3F1zcTFzrdWlVsU0wUWJoSqDMtegxj/B6xu7XqvlQAkgRMSPnT/8827Q728MCpJSXQzq8zarNZNBffFMHCRIDowZr3Vq4gz/4hIyg2reiMOloH6IPDyMvCZOdAtZTRJSqtqIEokWFKMXxv+/BPXbVhVMMK0otmWFIq+bSJF/TOO+XgeZ7pmwJ9vmUJ6fSu02/VZD+HW5CuAKT7Tdrm7dcUOzYR7Xdc3XkxGdnNwdCurTz+v/o99lHp6dBbO5OwdmZWcGfjk5eSrtd9t5/0IYB9v6m7m1bFG8Q1WBNmiUMq5qvX78F2GbEbsADAAA", "encoding": "utf-8"}, "headers": {"X-XSS-Protection": ["1; mode=block"], "Content-Security-Policy": ["default-src 'none'"], "Access-Control-Expose-Headers": ["ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"], "Transfer-Encoding": ["chunked"], "Access-Control-Allow-Origin": ["*"], "X-Frame-Options": ["deny"], "Status": ["200 OK"], "X-GitHub-Request-Id": ["95AE:3FAE:2C780CD:52C3D3D:5B640BE5"], "ETag": ["W/\"d2e139e7445377dc3ca16b5e4bf28495\""], "Date": ["Fri, 03 Aug 2018 08:01:41 GMT"], "X-RateLimit-Remaining": ["4996"], "Strict-Transport-Security": ["max-age=31536000; includeSubdomains; preload"], "Server": ["GitHub.com"], "X-OAuth-Scopes": ["public_repo, read:user"], "X-GitHub-Media-Type": ["github.v3; param=full; format=json"], "X-Content-Type-Options": ["nosniff"], "Content-Encoding": ["gzip"], "X-Runtime-rack": ["0.273701"], "Vary": ["Accept, Authorization, Cookie, X-GitHub-OTP"], "X-RateLimit-Limit": ["5000"], "Cache-Control": ["private, max-age=60, s-maxage=60"], "Referrer-Policy": ["origin-when-cross-origin, strict-origin-when-cross-origin"], "Content-Type": ["application/json; charset=utf-8"], "X-Accepted-OAuth-Scopes": [""], "X-RateLimit-Reset": ["1533285098"]}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/repos/sigmavirus24/github3.py/issues/711"}, "recorded_at": "2018-08-03T08:01:41"}], "recorded_with": "betamax/0.8.1"}
\ No newline at end of file
diff --git a/tests/integration/test_issue.py b/tests/integration/test_issue.py
index d9aadf2e..fc5426ad 100644
--- a/tests/integration/test_issue.py
+++ b/tests/integration/test_issue.py
@@ -11,6 +11,18 @@ class TestIssue(IntegrationHelper):
 
     """Integration tests for methods on the Issue class."""
 
+    def test_add_assignees(self):
+        """Test the ability to add assignees to an issue."""
+        self.auto_login()
+        cassette_name = self.cassette_name('add_assignees')
+        with self.recorder.use_cassette(cassette_name):
+            issue = self.gh.issue(username='sigmavirus24',
+                                  repository='github3.py',
+                                  number=711)
+            assigned = issue.add_assignees(['jacquerie'])
+
+        assert assigned is True
+
     def test_add_labels(self):
         """Test the ability to add a label to an issue."""
         self.auto_login()
@@ -203,6 +215,18 @@ class TestIssue(IntegrationHelper):
 
         assert reopened is True
 
+    def test_remove_assignees(self):
+        """Test the ability to remove assignees from an issue."""
+        self.auto_login()
+        cassette_name = self.cassette_name('remove_assignees')
+        with self.recorder.use_cassette(cassette_name):
+            issue = self.gh.issue(username='sigmavirus24',
+                                  repository='github3.py',
+                                  number=711)
+            unassigned = issue.remove_assignees(['jacquerie'])
+
+        assert unassigned is True
+
     def test_remove_label(self):
         """Test the ability to remove a label from an issue."""
         self.auto_login()
diff --git a/tests/unit/test_issues_issue.py b/tests/unit/test_issues_issue.py
index f8f1f0e0..f67cca29 100644
--- a/tests/unit/test_issues_issue.py
+++ b/tests/unit/test_issues_issue.py
@@ -44,6 +44,10 @@ class TestIssueRequiresAuth(helper.UnitRequiresAuthenticationHelper):
     def after_setup(self):
         self.session.has_auth.return_value = False
 
+    def test_add_assignees(self):
+        """Verify that adding assignees requires authentication."""
+        self.assert_requires_auth(self.instance.add_assignees)
+
     def test_add_labels(self):
         """Verify that adding a label requires authentication."""
         self.assert_requires_auth(self.instance.add_labels, 'enhancement')
@@ -73,6 +77,10 @@ class TestIssueRequiresAuth(helper.UnitRequiresAuthenticationHelper):
         """Verify that removing all labels requires authentication."""
         self.assert_requires_auth(self.instance.remove_all_labels)
 
+    def test_remove_assignees(self):
+        """Verify that removing assignees requires authentication."""
+        self.assert_requires_auth(self.instance.remove_assignees)
+
     def test_remove_label(self):
         """Verify that removing a label requires authentication."""
         self.assert_requires_auth(self.instance.remove_label, 'enhancement')
@@ -92,6 +100,15 @@ class TestIssue(helper.UnitHelper):
     described_class = github3.issues.Issue
     example_data = get_issue_example_data()
 
+    def test_add_assignees(self):
+        """Verify the request for adding assignees to an issue."""
+        self.instance.add_assignees(['jacquerie'])
+
+        self.session.patch.assert_called_with(
+            url_for(),
+            data='{"assignees": ["jacquerie"]}'
+        )
+
     def test_add_labels(self):
         """Verify the request for adding a label."""
         self.instance.add_labels('enhancement')
@@ -294,6 +311,15 @@ class TestIssue(helper.UnitHelper):
             assert self.instance.remove_all_labels() == []
             replace_labels.assert_called_once_with([])
 
+    def test_remove_assignees(self):
+        """Verify the request for removing assignees from an issue."""
+        self.instance.remove_assignees(['octocat'])
+
+        self.session.patch.assert_called_once_with(
+            url_for(),
+            data='{"assignees": []}'
+        )
+
     def test_remove_label(self):
         """Verify the request for removing a label from an issue."""
         self.instance.remove_label('enhancement')