py2docfx 0.1.9.dev1917798__py3-none-any.whl → 0.1.9.dev1926139__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. py2docfx/__main__.py +77 -22
  2. py2docfx/convert_prepare/environment.py +34 -13
  3. py2docfx/convert_prepare/generate_document.py +12 -5
  4. py2docfx/convert_prepare/get_source.py +5 -1
  5. py2docfx/convert_prepare/git.py +24 -20
  6. py2docfx/convert_prepare/package_info.py +15 -9
  7. py2docfx/convert_prepare/pip_utils.py +29 -8
  8. py2docfx/convert_prepare/post_process/merge_toc.py +5 -1
  9. py2docfx/convert_prepare/sphinx_caller.py +31 -14
  10. py2docfx/convert_prepare/tests/test_generate_document.py +2 -0
  11. py2docfx/convert_prepare/tests/test_sphinx_caller.py +2 -0
  12. py2docfx/convert_prepare/tests/utils.py +11 -0
  13. py2docfx/docfx_yaml/build_finished.py +11 -3
  14. py2docfx/docfx_yaml/convert_class.py +4 -2
  15. py2docfx/docfx_yaml/convert_enum.py +4 -2
  16. py2docfx/docfx_yaml/convert_module.py +4 -2
  17. py2docfx/docfx_yaml/convert_package.py +4 -2
  18. py2docfx/docfx_yaml/logger.py +68 -0
  19. py2docfx/docfx_yaml/process_doctree.py +6 -4
  20. py2docfx/docfx_yaml/translator.py +5 -7
  21. py2docfx/docfx_yaml/writer.py +10 -4
  22. py2docfx/docfx_yaml/yaml_builder.py +0 -1
  23. py2docfx/venv/basevenv/Lib/site-packages/setuptools/build_meta.py +2 -2
  24. py2docfx/venv/basevenv/Lib/site-packages/setuptools/command/bdist_egg.py +1 -1
  25. py2docfx/venv/basevenv/Lib/site-packages/setuptools/command/bdist_wheel.py +25 -39
  26. py2docfx/venv/basevenv/Lib/site-packages/setuptools/command/build_ext.py +2 -2
  27. py2docfx/venv/basevenv/Lib/site-packages/setuptools/command/build_py.py +9 -14
  28. py2docfx/venv/basevenv/Lib/site-packages/setuptools/command/easy_install.py +2 -2
  29. py2docfx/venv/basevenv/Lib/site-packages/setuptools/command/editable_wheel.py +2 -2
  30. py2docfx/venv/basevenv/Lib/site-packages/setuptools/command/egg_info.py +3 -2
  31. py2docfx/venv/basevenv/Lib/site-packages/setuptools/command/install_egg_info.py +2 -2
  32. py2docfx/venv/basevenv/Lib/site-packages/setuptools/command/saveopts.py +2 -2
  33. py2docfx/venv/basevenv/Lib/site-packages/setuptools/command/sdist.py +2 -2
  34. py2docfx/venv/basevenv/Lib/site-packages/setuptools/command/setopt.py +1 -1
  35. py2docfx/venv/basevenv/Lib/site-packages/setuptools/config/pyprojecttoml.py +0 -13
  36. py2docfx/venv/basevenv/Lib/site-packages/setuptools/dist.py +3 -2
  37. py2docfx/venv/basevenv/Lib/site-packages/setuptools/monkey.py +3 -3
  38. py2docfx/venv/basevenv/Lib/site-packages/setuptools/msvc.py +11 -11
  39. py2docfx/venv/basevenv/Lib/site-packages/setuptools/tests/config/test_pyprojecttoml.py +0 -7
  40. py2docfx/venv/basevenv/Lib/site-packages/setuptools/tests/test_core_metadata.py +168 -72
  41. py2docfx/venv/basevenv/Lib/site-packages/setuptools/unicode_utils.py +3 -3
  42. py2docfx/venv/basevenv/Lib/site-packages/wheel/__init__.py +1 -1
  43. py2docfx/venv/basevenv/Lib/site-packages/wheel/cli/convert.py +1 -2
  44. py2docfx/venv/venv1/Lib/site-packages/jwt/__init__.py +3 -2
  45. py2docfx/venv/venv1/Lib/site-packages/jwt/algorithms.py +31 -16
  46. py2docfx/venv/venv1/Lib/site-packages/jwt/api_jws.py +19 -8
  47. py2docfx/venv/venv1/Lib/site-packages/jwt/api_jwt.py +75 -19
  48. py2docfx/venv/venv1/Lib/site-packages/jwt/exceptions.py +8 -0
  49. py2docfx/venv/venv1/Lib/site-packages/jwt/help.py +4 -1
  50. py2docfx/venv/venv1/Lib/site-packages/jwt/jwks_client.py +4 -2
  51. py2docfx/venv/venv1/Lib/site-packages/jwt/utils.py +7 -10
  52. py2docfx/venv/venv1/Lib/site-packages/msal/application.py +1 -1
  53. py2docfx/venv/venv1/Lib/site-packages/msal/managed_identity.py +5 -3
  54. py2docfx/venv/venv1/Lib/site-packages/setuptools/build_meta.py +2 -2
  55. py2docfx/venv/venv1/Lib/site-packages/setuptools/command/bdist_egg.py +1 -1
  56. py2docfx/venv/venv1/Lib/site-packages/setuptools/command/bdist_wheel.py +25 -39
  57. py2docfx/venv/venv1/Lib/site-packages/setuptools/command/build_ext.py +2 -2
  58. py2docfx/venv/venv1/Lib/site-packages/setuptools/command/build_py.py +9 -14
  59. py2docfx/venv/venv1/Lib/site-packages/setuptools/command/easy_install.py +2 -2
  60. py2docfx/venv/venv1/Lib/site-packages/setuptools/command/editable_wheel.py +2 -2
  61. py2docfx/venv/venv1/Lib/site-packages/setuptools/command/egg_info.py +3 -2
  62. py2docfx/venv/venv1/Lib/site-packages/setuptools/command/install_egg_info.py +2 -2
  63. py2docfx/venv/venv1/Lib/site-packages/setuptools/command/saveopts.py +2 -2
  64. py2docfx/venv/venv1/Lib/site-packages/setuptools/command/sdist.py +2 -2
  65. py2docfx/venv/venv1/Lib/site-packages/setuptools/command/setopt.py +1 -1
  66. py2docfx/venv/venv1/Lib/site-packages/setuptools/config/pyprojecttoml.py +0 -13
  67. py2docfx/venv/venv1/Lib/site-packages/setuptools/dist.py +3 -2
  68. py2docfx/venv/venv1/Lib/site-packages/setuptools/monkey.py +3 -3
  69. py2docfx/venv/venv1/Lib/site-packages/setuptools/msvc.py +11 -11
  70. py2docfx/venv/venv1/Lib/site-packages/setuptools/tests/config/test_pyprojecttoml.py +0 -7
  71. py2docfx/venv/venv1/Lib/site-packages/setuptools/tests/test_core_metadata.py +168 -72
  72. py2docfx/venv/venv1/Lib/site-packages/setuptools/unicode_utils.py +3 -3
  73. py2docfx/venv/venv1/Lib/site-packages/wheel/__init__.py +1 -1
  74. py2docfx/venv/venv1/Lib/site-packages/wheel/cli/convert.py +1 -2
  75. {py2docfx-0.1.9.dev1917798.dist-info → py2docfx-0.1.9.dev1926139.dist-info}/METADATA +1 -1
  76. {py2docfx-0.1.9.dev1917798.dist-info → py2docfx-0.1.9.dev1926139.dist-info}/RECORD +79 -77
  77. {py2docfx-0.1.9.dev1917798.dist-info → py2docfx-0.1.9.dev1926139.dist-info}/WHEEL +1 -1
  78. /py2docfx/convert_prepare/conf_templates/{master_doc.rst_t → root_doc.rst_t} +0 -0
  79. {py2docfx-0.1.9.dev1917798.dist-info → py2docfx-0.1.9.dev1926139.dist-info}/top_level.txt +0 -0
@@ -122,8 +122,8 @@ class EggFileSource(ConvertSource):
122
122
  self.version = match.group("ver")
123
123
  if pyver := match.group("pyver"):
124
124
  self.pyver = pyver.replace(".", "")
125
- self.abi = self.pyver.replace("py", "cp")
126
125
  if arch := match.group("arch"):
126
+ self.abi = self.pyver.replace("py", "cp")
127
127
  self.platform = normalize(arch)
128
128
 
129
129
  self.metadata = Message()
@@ -225,7 +225,6 @@ class WininstFileSource(ConvertSource):
225
225
  self.platform = normalize(match.group("platform"))
226
226
  if pyver := match.group("pyver"):
227
227
  self.pyver = pyver.replace(".", "")
228
- self.abi = pyver.replace("py", "cp")
229
228
 
230
229
  # Look for an .egg-info directory and any .pyd files for more precise info
231
230
  egg_info_found = pyd_found = False
@@ -6,7 +6,7 @@ from .api_jws import (
6
6
  register_algorithm,
7
7
  unregister_algorithm,
8
8
  )
9
- from .api_jwt import PyJWT, decode, encode
9
+ from .api_jwt import PyJWT, decode, decode_complete, encode
10
10
  from .exceptions import (
11
11
  DecodeError,
12
12
  ExpiredSignatureError,
@@ -27,7 +27,7 @@ from .exceptions import (
27
27
  )
28
28
  from .jwks_client import PyJWKClient
29
29
 
30
- __version__ = "2.9.0"
30
+ __version__ = "2.10.0"
31
31
 
32
32
  __title__ = "PyJWT"
33
33
  __description__ = "JSON Web Token implementation in Python"
@@ -49,6 +49,7 @@ __all__ = [
49
49
  "PyJWK",
50
50
  "PyJWKSet",
51
51
  "decode",
52
+ "decode_complete",
52
53
  "encode",
53
54
  "get_unverified_header",
54
55
  "register_algorithm",
@@ -297,7 +297,7 @@ class HMACAlgorithm(Algorithm):
297
297
  else:
298
298
  raise ValueError
299
299
  except ValueError:
300
- raise InvalidKeyError("Key is not valid JSON")
300
+ raise InvalidKeyError("Key is not valid JSON") from None
301
301
 
302
302
  if obj.get("kty") != "oct":
303
303
  raise InvalidKeyError("Not an HMAC key")
@@ -346,7 +346,9 @@ if has_crypto:
346
346
  try:
347
347
  return cast(RSAPublicKey, load_pem_public_key(key_bytes))
348
348
  except (ValueError, UnsupportedAlgorithm):
349
- raise InvalidKeyError("Could not parse the provided public key.")
349
+ raise InvalidKeyError(
350
+ "Could not parse the provided public key."
351
+ ) from None
350
352
 
351
353
  @overload
352
354
  @staticmethod
@@ -409,10 +411,10 @@ if has_crypto:
409
411
  else:
410
412
  raise ValueError
411
413
  except ValueError:
412
- raise InvalidKeyError("Key is not valid JSON")
414
+ raise InvalidKeyError("Key is not valid JSON") from None
413
415
 
414
416
  if obj.get("kty") != "RSA":
415
- raise InvalidKeyError("Not an RSA key")
417
+ raise InvalidKeyError("Not an RSA key") from None
416
418
 
417
419
  if "d" in obj and "e" in obj and "n" in obj:
418
420
  # Private key
@@ -428,7 +430,7 @@ if has_crypto:
428
430
  if any_props_found and not all(props_found):
429
431
  raise InvalidKeyError(
430
432
  "RSA key must include all parameters if any are present besides d"
431
- )
433
+ ) from None
432
434
 
433
435
  public_numbers = RSAPublicNumbers(
434
436
  from_base64url_uint(obj["e"]),
@@ -520,7 +522,7 @@ if has_crypto:
520
522
  ):
521
523
  raise InvalidKeyError(
522
524
  "Expecting a EllipticCurvePrivateKey/EllipticCurvePublicKey. Wrong key provided for ECDSA algorithms"
523
- )
525
+ ) from None
524
526
 
525
527
  return crypto_key
526
528
 
@@ -581,13 +583,20 @@ if has_crypto:
581
583
  obj: dict[str, Any] = {
582
584
  "kty": "EC",
583
585
  "crv": crv,
584
- "x": to_base64url_uint(public_numbers.x).decode(),
585
- "y": to_base64url_uint(public_numbers.y).decode(),
586
+ "x": to_base64url_uint(
587
+ public_numbers.x,
588
+ bit_length=key_obj.curve.key_size,
589
+ ).decode(),
590
+ "y": to_base64url_uint(
591
+ public_numbers.y,
592
+ bit_length=key_obj.curve.key_size,
593
+ ).decode(),
586
594
  }
587
595
 
588
596
  if isinstance(key_obj, EllipticCurvePrivateKey):
589
597
  obj["d"] = to_base64url_uint(
590
- key_obj.private_numbers().private_value
598
+ key_obj.private_numbers().private_value,
599
+ bit_length=key_obj.curve.key_size,
591
600
  ).decode()
592
601
 
593
602
  if as_dict:
@@ -605,13 +614,13 @@ if has_crypto:
605
614
  else:
606
615
  raise ValueError
607
616
  except ValueError:
608
- raise InvalidKeyError("Key is not valid JSON")
617
+ raise InvalidKeyError("Key is not valid JSON") from None
609
618
 
610
619
  if obj.get("kty") != "EC":
611
- raise InvalidKeyError("Not an Elliptic curve key")
620
+ raise InvalidKeyError("Not an Elliptic curve key") from None
612
621
 
613
622
  if "x" not in obj or "y" not in obj:
614
- raise InvalidKeyError("Not an Elliptic curve key")
623
+ raise InvalidKeyError("Not an Elliptic curve key") from None
615
624
 
616
625
  x = base64url_decode(obj.get("x"))
617
626
  y = base64url_decode(obj.get("y"))
@@ -623,17 +632,23 @@ if has_crypto:
623
632
  if len(x) == len(y) == 32:
624
633
  curve_obj = SECP256R1()
625
634
  else:
626
- raise InvalidKeyError("Coords should be 32 bytes for curve P-256")
635
+ raise InvalidKeyError(
636
+ "Coords should be 32 bytes for curve P-256"
637
+ ) from None
627
638
  elif curve == "P-384":
628
639
  if len(x) == len(y) == 48:
629
640
  curve_obj = SECP384R1()
630
641
  else:
631
- raise InvalidKeyError("Coords should be 48 bytes for curve P-384")
642
+ raise InvalidKeyError(
643
+ "Coords should be 48 bytes for curve P-384"
644
+ ) from None
632
645
  elif curve == "P-521":
633
646
  if len(x) == len(y) == 66:
634
647
  curve_obj = SECP521R1()
635
648
  else:
636
- raise InvalidKeyError("Coords should be 66 bytes for curve P-521")
649
+ raise InvalidKeyError(
650
+ "Coords should be 66 bytes for curve P-521"
651
+ ) from None
637
652
  elif curve == "secp256k1":
638
653
  if len(x) == len(y) == 32:
639
654
  curve_obj = SECP256K1()
@@ -834,7 +849,7 @@ if has_crypto:
834
849
  else:
835
850
  raise ValueError
836
851
  except ValueError:
837
- raise InvalidKeyError("Key is not valid JSON")
852
+ raise InvalidKeyError("Key is not valid JSON") from None
838
853
 
839
854
  if obj.get("kty") != "OKP":
840
855
  raise InvalidKeyError("Not an Octet Key Pair")
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import binascii
4
4
  import json
5
5
  import warnings
6
+ from collections.abc import Sequence
6
7
  from typing import TYPE_CHECKING, Any
7
8
 
8
9
  from .algorithms import (
@@ -30,7 +31,7 @@ class PyJWS:
30
31
 
31
32
  def __init__(
32
33
  self,
33
- algorithms: list[str] | None = None,
34
+ algorithms: Sequence[str] | None = None,
34
35
  options: dict[str, Any] | None = None,
35
36
  ) -> None:
36
37
  self._algorithms = get_default_algorithms()
@@ -104,8 +105,8 @@ class PyJWS:
104
105
  def encode(
105
106
  self,
106
107
  payload: bytes,
107
- key: AllowedPrivateKeys | str | bytes,
108
- algorithm: str | None = "HS256",
108
+ key: AllowedPrivateKeys | PyJWK | str | bytes,
109
+ algorithm: str | None = None,
109
110
  headers: dict[str, Any] | None = None,
110
111
  json_encoder: type[json.JSONEncoder] | None = None,
111
112
  is_payload_detached: bool = False,
@@ -114,7 +115,13 @@ class PyJWS:
114
115
  segments = []
115
116
 
116
117
  # declare a new var to narrow the type for type checkers
117
- algorithm_: str = algorithm if algorithm is not None else "none"
118
+ if algorithm is None:
119
+ if isinstance(key, PyJWK):
120
+ algorithm_ = key.algorithm_name
121
+ else:
122
+ algorithm_ = "HS256"
123
+ else:
124
+ algorithm_ = algorithm
118
125
 
119
126
  # Prefer headers values if present to function parameters.
120
127
  if headers:
@@ -158,6 +165,8 @@ class PyJWS:
158
165
  signing_input = b".".join(segments)
159
166
 
160
167
  alg_obj = self.get_algorithm_by_name(algorithm_)
168
+ if isinstance(key, PyJWK):
169
+ key = key.key
161
170
  key = alg_obj.prepare_key(key)
162
171
  signature = alg_obj.sign(signing_input, key)
163
172
 
@@ -174,7 +183,7 @@ class PyJWS:
174
183
  self,
175
184
  jwt: str | bytes,
176
185
  key: AllowedPublicKeys | PyJWK | str | bytes = "",
177
- algorithms: list[str] | None = None,
186
+ algorithms: Sequence[str] | None = None,
178
187
  options: dict[str, Any] | None = None,
179
188
  detached_payload: bytes | None = None,
180
189
  **kwargs,
@@ -185,6 +194,7 @@ class PyJWS:
185
194
  "and will be removed in pyjwt version 3. "
186
195
  f"Unsupported kwargs: {tuple(kwargs.keys())}",
187
196
  RemovedInPyjwt3Warning,
197
+ stacklevel=2,
188
198
  )
189
199
  if options is None:
190
200
  options = {}
@@ -219,7 +229,7 @@ class PyJWS:
219
229
  self,
220
230
  jwt: str | bytes,
221
231
  key: AllowedPublicKeys | PyJWK | str | bytes = "",
222
- algorithms: list[str] | None = None,
232
+ algorithms: Sequence[str] | None = None,
223
233
  options: dict[str, Any] | None = None,
224
234
  detached_payload: bytes | None = None,
225
235
  **kwargs,
@@ -230,6 +240,7 @@ class PyJWS:
230
240
  "and will be removed in pyjwt version 3. "
231
241
  f"Unsupported kwargs: {tuple(kwargs.keys())}",
232
242
  RemovedInPyjwt3Warning,
243
+ stacklevel=2,
233
244
  )
234
245
  decoded = self.decode_complete(
235
246
  jwt, key, algorithms, options, detached_payload=detached_payload
@@ -291,14 +302,14 @@ class PyJWS:
291
302
  header: dict[str, Any],
292
303
  signature: bytes,
293
304
  key: AllowedPublicKeys | PyJWK | str | bytes = "",
294
- algorithms: list[str] | None = None,
305
+ algorithms: Sequence[str] | None = None,
295
306
  ) -> None:
296
307
  if algorithms is None and isinstance(key, PyJWK):
297
308
  algorithms = [key.algorithm_name]
298
309
  try:
299
310
  alg = header["alg"]
300
311
  except KeyError:
301
- raise InvalidAlgorithmError("Algorithm not specified")
312
+ raise InvalidAlgorithmError("Algorithm not specified") from None
302
313
 
303
314
  if not alg or (algorithms is not None and alg not in algorithms):
304
315
  raise InvalidAlgorithmError("The specified alg value is not allowed")
@@ -3,9 +3,9 @@ from __future__ import annotations
3
3
  import json
4
4
  import warnings
5
5
  from calendar import timegm
6
- from collections.abc import Iterable
6
+ from collections.abc import Iterable, Sequence
7
7
  from datetime import datetime, timedelta, timezone
8
- from typing import TYPE_CHECKING, Any, List
8
+ from typing import TYPE_CHECKING, Any
9
9
 
10
10
  from . import api_jws
11
11
  from .exceptions import (
@@ -15,6 +15,8 @@ from .exceptions import (
15
15
  InvalidAudienceError,
16
16
  InvalidIssuedAtError,
17
17
  InvalidIssuerError,
18
+ InvalidJTIError,
19
+ InvalidSubjectError,
18
20
  MissingRequiredClaimError,
19
21
  )
20
22
  from .warnings import RemovedInPyjwt3Warning
@@ -39,14 +41,16 @@ class PyJWT:
39
41
  "verify_iat": True,
40
42
  "verify_aud": True,
41
43
  "verify_iss": True,
44
+ "verify_sub": True,
45
+ "verify_jti": True,
42
46
  "require": [],
43
47
  }
44
48
 
45
49
  def encode(
46
50
  self,
47
51
  payload: dict[str, Any],
48
- key: AllowedPrivateKeys | str | bytes,
49
- algorithm: str | None = "HS256",
52
+ key: AllowedPrivateKeys | PyJWK | str | bytes,
53
+ algorithm: str | None = None,
50
54
  headers: dict[str, Any] | None = None,
51
55
  json_encoder: type[json.JSONEncoder] | None = None,
52
56
  sort_headers: bool = True,
@@ -102,7 +106,7 @@ class PyJWT:
102
106
  self,
103
107
  jwt: str | bytes,
104
108
  key: AllowedPublicKeys | PyJWK | str | bytes = "",
105
- algorithms: list[str] | None = None,
109
+ algorithms: Sequence[str] | None = None,
106
110
  options: dict[str, Any] | None = None,
107
111
  # deprecated arg, remove in pyjwt3
108
112
  verify: bool | None = None,
@@ -111,7 +115,8 @@ class PyJWT:
111
115
  # passthrough arguments to _validate_claims
112
116
  # consider putting in options
113
117
  audience: str | Iterable[str] | None = None,
114
- issuer: str | List[str] | None = None,
118
+ issuer: str | Sequence[str] | None = None,
119
+ subject: str | None = None,
115
120
  leeway: float | timedelta = 0,
116
121
  # kwargs
117
122
  **kwargs: Any,
@@ -122,6 +127,7 @@ class PyJWT:
122
127
  "and will be removed in pyjwt version 3. "
123
128
  f"Unsupported kwargs: {tuple(kwargs.keys())}",
124
129
  RemovedInPyjwt3Warning,
130
+ stacklevel=2,
125
131
  )
126
132
  options = dict(options or {}) # shallow-copy or initialize an empty dict
127
133
  options.setdefault("verify_signature", True)
@@ -135,6 +141,7 @@ class PyJWT:
135
141
  "The equivalent is setting `verify_signature` to False in the `options` dictionary. "
136
142
  "This invocation has a mismatch between the kwarg and the option entry.",
137
143
  category=DeprecationWarning,
144
+ stacklevel=2,
138
145
  )
139
146
 
140
147
  if not options["verify_signature"]:
@@ -143,11 +150,8 @@ class PyJWT:
143
150
  options.setdefault("verify_iat", False)
144
151
  options.setdefault("verify_aud", False)
145
152
  options.setdefault("verify_iss", False)
146
-
147
- if options["verify_signature"] and not algorithms:
148
- raise DecodeError(
149
- 'It is required that you pass in a value for the "algorithms" argument when calling decode().'
150
- )
153
+ options.setdefault("verify_sub", False)
154
+ options.setdefault("verify_jti", False)
151
155
 
152
156
  decoded = api_jws.decode_complete(
153
157
  jwt,
@@ -161,7 +165,12 @@ class PyJWT:
161
165
 
162
166
  merged_options = {**self.options, **options}
163
167
  self._validate_claims(
164
- payload, merged_options, audience=audience, issuer=issuer, leeway=leeway
168
+ payload,
169
+ merged_options,
170
+ audience=audience,
171
+ issuer=issuer,
172
+ leeway=leeway,
173
+ subject=subject,
165
174
  )
166
175
 
167
176
  decoded["payload"] = payload
@@ -178,7 +187,7 @@ class PyJWT:
178
187
  try:
179
188
  payload = json.loads(decoded["payload"])
180
189
  except ValueError as e:
181
- raise DecodeError(f"Invalid payload string: {e}")
190
+ raise DecodeError(f"Invalid payload string: {e}") from e
182
191
  if not isinstance(payload, dict):
183
192
  raise DecodeError("Invalid payload string: must be a json object")
184
193
  return payload
@@ -187,7 +196,7 @@ class PyJWT:
187
196
  self,
188
197
  jwt: str | bytes,
189
198
  key: AllowedPublicKeys | PyJWK | str | bytes = "",
190
- algorithms: list[str] | None = None,
199
+ algorithms: Sequence[str] | None = None,
191
200
  options: dict[str, Any] | None = None,
192
201
  # deprecated arg, remove in pyjwt3
193
202
  verify: bool | None = None,
@@ -196,7 +205,8 @@ class PyJWT:
196
205
  # passthrough arguments to _validate_claims
197
206
  # consider putting in options
198
207
  audience: str | Iterable[str] | None = None,
199
- issuer: str | List[str] | None = None,
208
+ subject: str | None = None,
209
+ issuer: str | Sequence[str] | None = None,
200
210
  leeway: float | timedelta = 0,
201
211
  # kwargs
202
212
  **kwargs: Any,
@@ -207,6 +217,7 @@ class PyJWT:
207
217
  "and will be removed in pyjwt version 3. "
208
218
  f"Unsupported kwargs: {tuple(kwargs.keys())}",
209
219
  RemovedInPyjwt3Warning,
220
+ stacklevel=2,
210
221
  )
211
222
  decoded = self.decode_complete(
212
223
  jwt,
@@ -216,6 +227,7 @@ class PyJWT:
216
227
  verify=verify,
217
228
  detached_payload=detached_payload,
218
229
  audience=audience,
230
+ subject=subject,
219
231
  issuer=issuer,
220
232
  leeway=leeway,
221
233
  )
@@ -227,6 +239,7 @@ class PyJWT:
227
239
  options: dict[str, Any],
228
240
  audience=None,
229
241
  issuer=None,
242
+ subject: str | None = None,
230
243
  leeway: float | timedelta = 0,
231
244
  ) -> None:
232
245
  if isinstance(leeway, timedelta):
@@ -256,6 +269,12 @@ class PyJWT:
256
269
  payload, audience, strict=options.get("strict_aud", False)
257
270
  )
258
271
 
272
+ if options["verify_sub"]:
273
+ self._validate_sub(payload, subject)
274
+
275
+ if options["verify_jti"]:
276
+ self._validate_jti(payload)
277
+
259
278
  def _validate_required_claims(
260
279
  self,
261
280
  payload: dict[str, Any],
@@ -265,6 +284,39 @@ class PyJWT:
265
284
  if payload.get(claim) is None:
266
285
  raise MissingRequiredClaimError(claim)
267
286
 
287
+ def _validate_sub(self, payload: dict[str, Any], subject=None) -> None:
288
+ """
289
+ Checks whether "sub" if in the payload is valid ot not.
290
+ This is an Optional claim
291
+
292
+ :param payload(dict): The payload which needs to be validated
293
+ :param subject(str): The subject of the token
294
+ """
295
+
296
+ if "sub" not in payload:
297
+ return
298
+
299
+ if not isinstance(payload["sub"], str):
300
+ raise InvalidSubjectError("Subject must be a string")
301
+
302
+ if subject is not None:
303
+ if payload.get("sub") != subject:
304
+ raise InvalidSubjectError("Invalid subject")
305
+
306
+ def _validate_jti(self, payload: dict[str, Any]) -> None:
307
+ """
308
+ Checks whether "jti" if in the payload is valid ot not
309
+ This is an Optional claim
310
+
311
+ :param payload(dict): The payload which needs to be validated
312
+ """
313
+
314
+ if "jti" not in payload:
315
+ return
316
+
317
+ if not isinstance(payload.get("jti"), str):
318
+ raise InvalidJTIError("JWT ID must be a string")
319
+
268
320
  def _validate_iat(
269
321
  self,
270
322
  payload: dict[str, Any],
@@ -274,7 +326,9 @@ class PyJWT:
274
326
  try:
275
327
  iat = int(payload["iat"])
276
328
  except ValueError:
277
- raise InvalidIssuedAtError("Issued At claim (iat) must be an integer.")
329
+ raise InvalidIssuedAtError(
330
+ "Issued At claim (iat) must be an integer."
331
+ ) from None
278
332
  if iat > (now + leeway):
279
333
  raise ImmatureSignatureError("The token is not yet valid (iat)")
280
334
 
@@ -287,7 +341,7 @@ class PyJWT:
287
341
  try:
288
342
  nbf = int(payload["nbf"])
289
343
  except ValueError:
290
- raise DecodeError("Not Before claim (nbf) must be an integer.")
344
+ raise DecodeError("Not Before claim (nbf) must be an integer.") from None
291
345
 
292
346
  if nbf > (now + leeway):
293
347
  raise ImmatureSignatureError("The token is not yet valid (nbf)")
@@ -301,7 +355,9 @@ class PyJWT:
301
355
  try:
302
356
  exp = int(payload["exp"])
303
357
  except ValueError:
304
- raise DecodeError("Expiration Time claim (exp) must be an integer.")
358
+ raise DecodeError(
359
+ "Expiration Time claim (exp) must be an integer."
360
+ ) from None
305
361
 
306
362
  if exp <= (now - leeway):
307
363
  raise ExpiredSignatureError("Signature has expired")
@@ -363,7 +419,7 @@ class PyJWT:
363
419
  if "iss" not in payload:
364
420
  raise MissingRequiredClaimError("iss")
365
421
 
366
- if isinstance(issuer, list):
422
+ if isinstance(issuer, Sequence):
367
423
  if payload["iss"] not in issuer:
368
424
  raise InvalidIssuerError("Invalid issuer")
369
425
  else:
@@ -72,3 +72,11 @@ class PyJWKClientError(PyJWTError):
72
72
 
73
73
  class PyJWKClientConnectionError(PyJWKClientError):
74
74
  pass
75
+
76
+
77
+ class InvalidSubjectError(InvalidTokenError):
78
+ pass
79
+
80
+
81
+ class InvalidJTIError(InvalidTokenError):
82
+ pass
@@ -39,7 +39,10 @@ def info() -> Dict[str, Dict[str, str]]:
39
39
  )
40
40
  if pypy_version_info.releaselevel != "final":
41
41
  implementation_version = "".join(
42
- [implementation_version, pypy_version_info.releaselevel]
42
+ [
43
+ implementation_version,
44
+ pypy_version_info.releaselevel,
45
+ ]
43
46
  )
44
47
  else:
45
48
  implementation_version = "Unknown"
@@ -45,7 +45,9 @@ class PyJWKClient:
45
45
  if cache_keys:
46
46
  # Cache signing keys
47
47
  # Ignore mypy (https://github.com/python/mypy/issues/2427)
48
- self.get_signing_key = lru_cache(maxsize=max_cached_keys)(self.get_signing_key) # type: ignore
48
+ self.get_signing_key = lru_cache(maxsize=max_cached_keys)(
49
+ self.get_signing_key
50
+ ) # type: ignore
49
51
 
50
52
  def fetch_data(self) -> Any:
51
53
  jwk_set: Any = None
@@ -58,7 +60,7 @@ class PyJWKClient:
58
60
  except (URLError, TimeoutError) as e:
59
61
  raise PyJWKClientConnectionError(
60
62
  f'Fail to fetch data from the url, err: "{e}"'
61
- )
63
+ ) from e
62
64
  else:
63
65
  return jwk_set
64
66
  finally:
@@ -1,7 +1,7 @@
1
1
  import base64
2
2
  import binascii
3
3
  import re
4
- from typing import Union
4
+ from typing import Optional, Union
5
5
 
6
6
  try:
7
7
  from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve
@@ -37,11 +37,11 @@ def base64url_encode(input: bytes) -> bytes:
37
37
  return base64.urlsafe_b64encode(input).replace(b"=", b"")
38
38
 
39
39
 
40
- def to_base64url_uint(val: int) -> bytes:
40
+ def to_base64url_uint(val: int, *, bit_length: Optional[int] = None) -> bytes:
41
41
  if val < 0:
42
42
  raise ValueError("Must be a positive integer")
43
43
 
44
- int_bytes = bytes_from_int(val)
44
+ int_bytes = bytes_from_int(val, bit_length=bit_length)
45
45
 
46
46
  if len(int_bytes) == 0:
47
47
  int_bytes = b"\x00"
@@ -63,13 +63,10 @@ def bytes_to_number(string: bytes) -> int:
63
63
  return int(binascii.b2a_hex(string), 16)
64
64
 
65
65
 
66
- def bytes_from_int(val: int) -> bytes:
67
- remaining = val
68
- byte_length = 0
69
-
70
- while remaining != 0:
71
- remaining >>= 8
72
- byte_length += 1
66
+ def bytes_from_int(val: int, *, bit_length: Optional[int] = None) -> bytes:
67
+ if bit_length is None:
68
+ bit_length = val.bit_length()
69
+ byte_length = (bit_length + 7) // 8
73
70
 
74
71
  return val.to_bytes(byte_length, "big", signed=False)
75
72
 
@@ -21,7 +21,7 @@ from .cloudshell import _is_running_in_cloud_shell
21
21
 
22
22
 
23
23
  # The __init__.py will import this. Not the other way around.
24
- __version__ = "1.31.0" # When releasing, also check and bump our dependencies's versions if needed
24
+ __version__ = "1.31.1" # When releasing, also check and bump our dependencies's versions if needed
25
25
 
26
26
  logger = logging.getLogger(__name__)
27
27
  _AUTHORITY_TYPE_CLOUDSHELL = "CLOUDSHELL"
@@ -346,10 +346,12 @@ def _scope_to_resource(scope): # This is an experimental reasonable-effort appr
346
346
  def _get_arc_endpoint():
347
347
  if "IDENTITY_ENDPOINT" in os.environ and "IMDS_ENDPOINT" in os.environ:
348
348
  return os.environ["IDENTITY_ENDPOINT"]
349
- if ( # Defined in https://msazure.visualstudio.com/One/_wiki/wikis/One.wiki/233012/VM-Extension-Authoring-for-Arc?anchor=determining-which-endpoint-to-use
350
- sys.platform == "linux" and os.path.exists("/var/opt/azcmagent/bin/himds")
349
+ if ( # Defined in https://eng.ms/docs/cloud-ai-platform/azure-core/azure-management-and-platforms/control-plane-bburns/hybrid-resource-provider/azure-arc-for-servers/specs/extension_authoring
350
+ sys.platform == "linux" and os.path.exists("/opt/azcmagent/bin/himds")
351
351
  or sys.platform == "win32" and os.path.exists(os.path.expandvars(
352
- r"%ProgramFiles%\AzureConnectedMachineAgent\himds.exe"))
352
+ # Avoid Windows-only "%EnvVar%" syntax so that tests can be run on Linux
353
+ r"${ProgramFiles}\AzureConnectedMachineAgent\himds.exe"
354
+ ))
353
355
  ):
354
356
  return "http://localhost:40342/metadata/identity/oauth2/token"
355
357
 
@@ -91,11 +91,11 @@ class Distribution(setuptools.dist.Distribution):
91
91
  for the duration of this context.
92
92
  """
93
93
  orig = distutils.core.Distribution
94
- distutils.core.Distribution = cls
94
+ distutils.core.Distribution = cls # type: ignore[misc] # monkeypatching
95
95
  try:
96
96
  yield
97
97
  finally:
98
- distutils.core.Distribution = orig
98
+ distutils.core.Distribution = orig # type: ignore[misc] # monkeypatching
99
99
 
100
100
 
101
101
  @contextlib.contextmanager
@@ -277,7 +277,7 @@ class bdist_egg(Command):
277
277
  log.warn("zip_safe flag not set; analyzing archive contents...")
278
278
  return analyze_egg(self.bdist_dir, self.stubs)
279
279
 
280
- def gen_header(self) -> str:
280
+ def gen_header(self) -> Literal["w"]:
281
281
  return 'w'
282
282
 
283
283
  def copy_metadata_to(self, target_dir) -> None: