Skip to content

Commit 7c6de8c

Browse files
committed
fix: use python>=3.11, improve type hints
1 parent 43af5af commit 7c6de8c

14 files changed

Lines changed: 83 additions & 78 deletions

File tree

‎.github/workflows/test.yml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ jobs:
5252
max-parallel: 3
5353
matrix:
5454
python-version:
55-
- "3.10"
5655
- "3.11"
5756
- "3.12"
5857
- "3.13"

‎docs/changelog.rst‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ Changelog
1212
.. module:: joserfc
1313
:noindex:
1414

15+
Unreleased
16+
----------
17+
18+
- Drop Python 3.10 support; Python 3.11 or newer is required.
19+
- Preserve key subclasses in factory return types with ``typing.Self``.
20+
- Improve claim validation type narrowing with ``typing.TypeGuard`` and refine
21+
password, native key, and equality annotations.
22+
1523
1.7.7
1624
-----
1725

‎pyproject.toml‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ dependencies = [
66
"cryptography>=45.0.1",
77
]
88
license = {text = "BSD-3-Clause"}
9-
requires-python = ">=3.10"
9+
requires-python = ">=3.11"
1010
dynamic = ["version"]
1111
readme = "README.rst"
1212
classifiers = [
@@ -18,7 +18,6 @@ classifiers = [
1818
"Operating System :: OS Independent",
1919
"Programming Language :: Python",
2020
"Programming Language :: Python :: 3",
21-
"Programming Language :: Python :: 3.10",
2221
"Programming Language :: Python :: 3.11",
2322
"Programming Language :: Python :: 3.12",
2423
"Programming Language :: Python :: 3.13",
@@ -75,6 +74,7 @@ docs = [
7574

7675
[tool.ruff]
7776
line-length = 120
77+
target-version = "py311"
7878

7979
[tool.pytest.ini_options]
8080
pythonpath = ["src", "."]
@@ -105,7 +105,7 @@ exclude_lines = [
105105

106106
[tool.mypy]
107107
strict = true
108-
python_version = "3.10"
108+
python_version = "3.11"
109109
files = ["src/joserfc"]
110110
show_error_codes = true
111111
pretty = true
@@ -114,7 +114,6 @@ pretty = true
114114
requires = ["tox>=4.19"]
115115
env_list = [
116116
"style",
117-
"py310",
118117
"py311",
119118
"py312",
120119
"py313",

‎requirements-dev.lock‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ colorama==0.4.6 ; sys_platform == 'win32'
88
coverage==7.15.0
99
cryptography==49.0.0
1010
distlib==0.4.3
11-
exceptiongroup==1.3.1 ; python_full_version < '3.11'
1211
filelock==3.29.7
1312
identify==2.6.19
1413
iniconfig==2.3.0

‎requirements-docs.lock‎

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,21 @@ colorama==0.4.6 ; sys_platform == 'win32'
1313
coverage==7.15.0
1414
cryptography==49.0.0
1515
distlib==0.4.3
16-
docutils==0.21.2 ; python_full_version < '3.11'
17-
docutils==0.22.4 ; python_full_version >= '3.11'
18-
exceptiongroup==1.3.1 ; python_full_version < '3.11'
16+
docutils==0.22.4
1917
filelock==3.29.7
2018
identify==2.6.19
2119
idna==3.18
2220
imagesize==2.0.0
2321
iniconfig==2.3.0
2422
jinja2==3.1.6
2523
librt==0.13.0 ; platform_python_implementation != 'PyPy'
26-
markdown-it-py==3.0.0 ; python_full_version < '3.11'
27-
markdown-it-py==4.2.0 ; python_full_version >= '3.11'
24+
markdown-it-py==4.2.0
2825
markupsafe==3.0.3
2926
mdit-py-plugins==0.6.1
3027
mdurl==0.1.2
3128
mypy==2.2.0
3229
mypy-extensions==1.1.0
33-
myst-parser==4.0.1 ; python_full_version < '3.11'
34-
myst-parser==5.1.0 ; python_full_version >= '3.11'
30+
myst-parser==5.1.0
3531
nodeenv==1.10.0
3632
packaging==26.2
3733
pathspec==1.1.1
@@ -47,17 +43,15 @@ pytest-cov==7.1.0
4743
python-discovery==1.4.3
4844
pyyaml==6.0.3
4945
requests==2.34.2
50-
roman-numerals==4.1.0 ; python_full_version >= '3.11'
46+
roman-numerals==4.1.0
5147
ruff==0.15.20
5248
shibuya==2026.7.8
5349
snowballstemmer==3.1.1
54-
sphinx==8.1.3 ; python_full_version < '3.11'
5550
sphinx==9.0.4 ; python_full_version == '3.11.*'
5651
sphinx==9.1.0 ; python_full_version >= '3.12'
5752
sphinx-contributors==0.3.0
5853
sphinx-copybutton==0.5.2
59-
sphinx-design==0.6.1 ; python_full_version < '3.11'
60-
sphinx-design==0.7.0 ; python_full_version >= '3.11'
54+
sphinx-design==0.7.0
6155
sphinx-iconify==0.3.0
6256
sphinx-intl==2.3.2
6357
sphinx-last-updated-by-git==0.3.8

‎sonar-project.properties‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ sonar.sources=src
55
sonar.sourceEncoding=UTF-8
66
sonar.test.inclusions=tests/**/test_*.py
77

8-
sonar.python.version=3.10, 3.11, 3.12, 3.13, 3.14
8+
sonar.python.version=3.11, 3.12, 3.13, 3.14
99
sonar.python.coverage.reportPaths=coverage.xml

‎src/joserfc/_keys.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def __iter__(self) -> t.Iterator[Key]:
123123
def __bool__(self) -> bool:
124124
return bool(self.keys)
125125

126-
def __eq__(self, other: t.Any) -> bool:
126+
def __eq__(self, other: object) -> bool:
127127
if not isinstance(other, KeySet):
128128
return False
129129
return self.keys == other.keys
@@ -164,7 +164,7 @@ def pick_random_key(self, algorithm: str | None = None, parameters: KeyParameter
164164
return None
165165

166166
@classmethod
167-
def import_key_set(cls, value: KeySetSerialization, parameters: KeyParameters | None = None) -> "KeySet":
167+
def import_key_set(cls, value: KeySetSerialization, parameters: KeyParameters | None = None) -> t.Self:
168168
keys: list[Key] = []
169169

170170
for data in value["keys"]:
@@ -189,7 +189,7 @@ def generate_key_set(
189189
parameters: KeyParameters | None = None,
190190
private: bool = True,
191191
count: int = 4,
192-
) -> "KeySet":
192+
) -> t.Self:
193193
keys: list[Key] = []
194194
for _ in range(count):
195195
key = cls.registry_cls.generate_key(key_type, crv_or_size, parameters, private)

‎src/joserfc/_rfc7517/models.py‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def import_from_dict(cls, value: DictKey) -> t.Any:
4242

4343
@classmethod
4444
@abstractmethod
45-
def import_from_bytes(cls, value: bytes, password: t.Any = None) -> t.Any:
45+
def import_from_bytes(cls, value: bytes, password: str | bytes | None = None) -> t.Any:
4646
pass
4747

4848
@staticmethod
@@ -102,7 +102,7 @@ def __init__(
102102
self.validate_dict_key(data)
103103
self._dict_value = data
104104

105-
def __eq__(self, other: t.Any) -> bool:
105+
def __eq__(self, other: object) -> bool:
106106
if not isinstance(other, self.__class__):
107107
return False
108108
return self.dict_value == other.dict_value
@@ -134,7 +134,7 @@ def alg(self) -> str | None:
134134
return t.cast(str | None, self.get("alg"))
135135

136136
@property
137-
def raw_value(self) -> t.Any:
137+
def raw_value(self) -> NativePrivateKey | NativePublicKey:
138138
raise NotImplementedError()
139139

140140
@property
@@ -268,11 +268,11 @@ def validate_dict_key(cls, data: DictKey) -> None:
268268

269269
@classmethod
270270
def import_key(
271-
cls: type[GenericKey],
271+
cls,
272272
value: AnyKey,
273273
parameters: KeyParameters | None = None,
274-
password: t.Any = None,
275-
) -> GenericKey:
274+
password: str | bytes | None = None,
275+
) -> t.Self:
276276
if isinstance(value, dict):
277277
cls.validate_dict_key(value)
278278
raw_key = cls.binding.import_from_dict(value)
@@ -283,12 +283,12 @@ def import_key(
283283

284284
@classmethod
285285
def generate_key(
286-
cls: type[GenericKey],
286+
cls,
287287
*,
288288
parameters: KeyParameters | None = None,
289289
private: bool = True,
290290
auto_kid: bool = False,
291-
) -> GenericKey:
291+
) -> t.Self:
292292
raise NotImplementedError()
293293

294294

‎src/joserfc/_rfc7517/pem.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def dump_pem_key(
5353
key: Any,
5454
encoding: Literal["PEM", "DER"] | None = None,
5555
private: bool | None = False,
56-
password: Any | None = None,
56+
password: str | bytes | None = None,
5757
) -> bytes:
5858
"""Export key into PEM/DER format bytes.
5959
@@ -118,7 +118,7 @@ def import_from_dict(cls, value: DictKey) -> Any:
118118
return cls.import_public_key(value)
119119

120120
@classmethod
121-
def import_from_bytes(cls, value: bytes, password: Any | None = None) -> Any:
121+
def import_from_bytes(cls, value: bytes, password: str | bytes | None = None) -> Any:
122122
if cls.check_ssh_type(value):
123123
return import_from_ssh_key(value)
124124

‎src/joserfc/_rfc7518/ec_key.py‎

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -153,26 +153,26 @@ def curve_key_size(self) -> int:
153153

154154
@classmethod
155155
def import_key(
156-
cls: t.Any,
156+
cls,
157157
value: AnyKey | EllipticCurvePrivateKey | EllipticCurvePublicKey,
158158
parameters: KeyParameters | None = None,
159-
password: t.Any = None,
160-
) -> "ECKey":
161-
key: ECKey
159+
password: str | bytes | None = None,
160+
) -> t.Self:
161+
key: t.Self
162162
if isinstance(value, (EllipticCurvePrivateKey, EllipticCurvePublicKey)):
163163
key = cls(value, value, parameters)
164164
else:
165-
key = super(ECKey, cls).import_key(value, parameters, password)
165+
key = super().import_key(value, parameters, password)
166166
return key
167167

168168
@classmethod
169169
def generate_key(
170-
cls: type["ECKey"],
170+
cls,
171171
crv: str | None = "P-256",
172172
parameters: KeyParameters | None = None,
173173
private: bool = True,
174174
auto_kid: bool = False,
175-
) -> "ECKey":
175+
) -> t.Self:
176176
"""Generate a ``ECKey`` with the given "crv" value.
177177
178178
:param crv: ECKey curve name
@@ -187,15 +187,15 @@ def generate_key(
187187

188188
@classmethod
189189
def derive_key(
190-
cls: type["ECKey"],
190+
cls,
191191
secret: bytes | str,
192192
crv: str = "P-256",
193193
parameters: KeyParameters | None = None,
194194
private: bool = True,
195195
auto_kid: bool = False,
196196
kdf_name: t.Literal["HKDF", "PBKDF2"] = "HKDF",
197197
kdf_options: dict[str, t.Any] | None = None,
198-
) -> "ECKey":
198+
) -> t.Self:
199199
"""
200200
Generate an elliptic curve cryptographic key derived from a secret input using a key
201201
derivation function (KDF). This allows the creation of deterministic elliptic curve
@@ -249,13 +249,16 @@ def derive_key(
249249
return _wrap_key(cls, raw_key, private, auto_kid, parameters)
250250

251251

252+
ECKeyType = t.TypeVar("ECKeyType", bound=ECKey)
253+
254+
252255
def _wrap_key(
253-
cls: type["ECKey"],
256+
cls: type[ECKeyType],
254257
raw_key: EllipticCurvePrivateKey,
255258
private: bool,
256259
auto_kid: bool,
257260
parameters: KeyParameters | None = None,
258-
) -> ECKey:
261+
) -> ECKeyType:
259262
if private:
260263
key = cls(raw_key, raw_key, parameters)
261264
else:

0 commit comments

Comments
 (0)