Repository: jpadilla/pyjwt
Pull request #782 (2022-07-21 15:55:33)

Update audience typing
Hello :wave: 

Using the library, I just noticed that the typing of `audience` was not that clear.
There is a difference between:
- [annotations](https://github.com/jpadilla/pyjwt/blob/master/jwt/api_jwt.py#L153)
- [type checking](https://github.com/jpadilla/pyjwt/blob/master/jwt/api_jwt.py#L183)
- [documentation](https://github.com/jpadilla/pyjwt/blob/master/docs/api.rst#L65)

This PR aims to bring everything in line.

I removed `bytes` from the type checking, but according to the test it was not handled (raising error [here](https://github.com/jpadilla/pyjwt/blob/master/jwt/api_jwt.py#L260)).



Patch:
diff --git a/docs/api.rst b/docs/api.rst
index 2f81b1f..919b6af 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -62,7 +62,7 @@ API Reference
             if ``verify_exp``, ``verify_iat``, and ``verify_nbf`` respectively
             is set to ``True``).
 
-    :param Iterable audience: optional, the value for ``verify_aud`` check
+    :param Union[str, Iterable] audience: optional, the value for ``verify_aud`` check
     :param str issuer: optional, the value for ``verify_iss`` check
     :param float leeway: a time margin in seconds for the expiration check
     :rtype: dict
diff --git a/jwt/api_jwt.py b/jwt/api_jwt.py
index b08e950..91a6d2e 100644
--- a/jwt/api_jwt.py
+++ b/jwt/api_jwt.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 import json
 import warnings
 from calendar import timegm
@@ -76,7 +78,7 @@ class PyJWT:
         detached_payload: Optional[bytes] = None,
         # passthrough arguments to _validate_claims
         # consider putting in options
-        audience: Optional[str] = None,
+        audience: Optional[Union[str, Iterable[str]]] = None,
         issuer: Optional[str] = None,
         leeway: Union[int, float, timedelta] = 0,
         # kwargs
@@ -150,7 +152,7 @@ class PyJWT:
         detached_payload: Optional[bytes] = None,
         # passthrough arguments to _validate_claims
         # consider putting in options
-        audience: Optional[str] = None,
+        audience: Optional[Union[str, Iterable[str]]] = None,
         issuer: Optional[str] = None,
         leeway: Union[int, float, timedelta] = 0,
         # kwargs
@@ -180,8 +182,8 @@ class PyJWT:
         if isinstance(leeway, timedelta):
             leeway = leeway.total_seconds()
 
-        if not isinstance(audience, (bytes, str, type(None), Iterable)):
-            raise TypeError("audience must be a string, iterable, or None")
+        if audience is not None and not isinstance(audience, (str, Iterable)):
+            raise TypeError("audience must be a string, iterable or None")
 
         self._validate_required_claims(payload, options)
 


Test patch:
diff --git a/tests/test_api_jwt.py b/tests/test_api_jwt.py
index d0443e8..bebe7d2 100644
--- a/tests/test_api_jwt.py
+++ b/tests/test_api_jwt.py
@@ -119,7 +119,7 @@ class TestJWT:
             jwt.decode(example_jwt, secret, audience=1, algorithms=["HS256"])
 
         exception = context.value
-        assert str(exception) == "audience must be a string, iterable, or None"
+        assert str(exception) == "audience must be a string, iterable or None"
 
     def test_decode_with_nonlist_aud_claim_throws_exception(self, jwt):
         secret = "secret"
@@ -419,6 +419,14 @@ class TestJWT:
         with pytest.raises(InvalidAudienceError):
             jwt.decode(token, "secret", audience="urn-me", algorithms=["HS256"])
 
+    def test_raise_exception_audience_as_bytes(self, jwt):
+        payload = {"some": "payload", "aud": ["urn:me", "urn:someone-else"]}
+        token = jwt.encode(payload, "secret")
+        with pytest.raises(InvalidAudienceError):
+            jwt.decode(
+                token, "secret", audience="urn:me".encode(), algorithms=["HS256"]
+            )
+
     def test_raise_exception_invalid_audience_in_array(self, jwt):
         payload = {
             "some": "payload",