Skip to content

Commit 01455c2

Browse files
Improve typing (#24)
1 parent d41f769 commit 01455c2

11 files changed

Lines changed: 97 additions & 96 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.1.1] - 2026-03-??
9+
10+
- Modernize type annotations: replace `Union[X, Y]` with `X | Y`, `Optional[X]` with `X | None`, and built-in generics (`dict`, `list`, `set`) instead of their `typing` counterparts. Requires Python 3.10+.
11+
812
## [1.1.0] - 2026-03-10
913

1014
- Add support for ES* algorithms (`ES256`, `ES384`, `ES512`) for EC keys in

guardpost/abc.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from abc import ABC
2-
from typing import Any, Iterable, List, Optional, Type, TypeVar, Union
2+
from typing import Any, Iterable, Type, TypeVar
33

44
from rodi import ContainerProtocol
55

@@ -19,7 +19,7 @@ def __init__(self) -> None:
1919

2020

2121
class BaseStrategy(ABC):
22-
def __init__(self, container: Optional[ContainerProtocol] = None) -> None:
22+
def __init__(self, container: ContainerProtocol | None = None) -> None:
2323
super().__init__()
2424
self._container = container
2525

@@ -39,7 +39,7 @@ def _get_di_scope(self, scope: Any):
3939
except AttributeError:
4040
return None
4141

42-
def _get_instances(self, items: List[Union[T, Type[T]]], scope: Any) -> Iterable[T]:
42+
def _get_instances(self, items: list[T | Type[T]], scope: Any) -> Iterable[T]:
4343
"""
4444
Yields instances of types, optionally activated through dependency injection.
4545

guardpost/authentication.py

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from abc import ABC, abstractmethod
44
from functools import lru_cache
55
from logging import Logger
6-
from typing import Any, List, Optional, Sequence, Type, Union
6+
from typing import Any, Sequence, Type
77

88
from rodi import ContainerProtocol
99

@@ -19,20 +19,20 @@ class Identity:
1919

2020
def __init__(
2121
self,
22-
claims: Optional[dict] = None,
23-
authentication_mode: Optional[str] = None,
22+
claims: dict | None = None,
23+
authentication_mode: str | None = None,
2424
):
2525
self.claims = claims or {}
2626
self.authentication_mode = authentication_mode
27-
self.access_token: Optional[str] = None
28-
self.refresh_token: Optional[str] = None
27+
self.access_token: str | None = None
28+
self.refresh_token: str | None = None
2929

3030
@property
31-
def sub(self) -> Optional[str]:
31+
def sub(self) -> str | None:
3232
return self.get("sub")
3333

3434
@property
35-
def roles(self) -> Optional[str]:
35+
def roles(self) -> str | None:
3636
return self.get("roles")
3737

3838
def is_authenticated(self) -> bool:
@@ -58,15 +58,15 @@ def has_role(self, name: str) -> bool:
5858

5959
class User(Identity):
6060
@property
61-
def id(self) -> Optional[str]:
61+
def id(self) -> str | None:
6262
return self.get("id") or self.sub
6363

6464
@property
65-
def name(self) -> Optional[str]:
65+
def name(self) -> str | None:
6666
return self.get("name")
6767

6868
@property
69-
def email(self) -> Optional[str]:
69+
def email(self) -> str | None:
7070
return self.get("email")
7171

7272

@@ -79,7 +79,7 @@ def scheme(self) -> str:
7979
return self.__class__.__name__
8080

8181
@abstractmethod
82-
def authenticate(self, context: Any) -> Optional[Identity]:
82+
def authenticate(self, context: Any) -> Identity | None:
8383
"""Obtains an identity from a context."""
8484

8585

@@ -90,9 +90,7 @@ def _is_async_handler(handler_type: Type[AuthenticationHandler]) -> bool:
9090
return inspect.iscoroutinefunction(handler_type.authenticate)
9191

9292

93-
AuthenticationHandlerConfType = Union[
94-
AuthenticationHandler, Type[AuthenticationHandler]
95-
]
93+
AuthenticationHandlerConfType = AuthenticationHandler | Type[AuthenticationHandler]
9694

9795

9896
class AuthenticationSchemesNotFound(ValueError):
@@ -110,9 +108,9 @@ class AuthenticationStrategy(BaseStrategy):
110108
def __init__(
111109
self,
112110
*handlers: AuthenticationHandlerConfType,
113-
container: Optional[ContainerProtocol] = None,
114-
rate_limiter: Optional[RateLimiter] = None,
115-
logger: Optional[Logger] = None,
111+
container: ContainerProtocol | None = None,
112+
rate_limiter: RateLimiter | None = None,
113+
logger: Logger | None = None,
116114
):
117115
"""
118116
Initializes an AuthenticationStrategy instance.
@@ -144,9 +142,9 @@ def __iadd__(
144142

145143
def _get_handlers_by_schemes(
146144
self,
147-
authentication_schemes: Optional[Sequence[str]] = None,
145+
authentication_schemes: Sequence[str] | None = None,
148146
context: Any = None,
149-
) -> List[AuthenticationHandler]:
147+
) -> list[AuthenticationHandler]:
150148
if not authentication_schemes:
151149
return list(self._get_instances(self.handlers, context))
152150

@@ -168,8 +166,8 @@ def _get_handlers_by_schemes(
168166
return handlers
169167

170168
async def authenticate(
171-
self, context: Any, authentication_schemes: Optional[Sequence[str]] = None
172-
) -> Optional[Identity]:
169+
self, context: Any, authentication_schemes: Sequence[str] | None = None
170+
) -> Identity | None:
173171
"""
174172
Tries to obtain the user for a context, applying authentication rules and
175173
optional rate limiting.

guardpost/authorization.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import inspect
22
from abc import ABC, abstractmethod
33
from functools import lru_cache, wraps
4-
from typing import Any, Callable, Iterable, List, Optional, Sequence, Set, Type, Union
4+
from typing import Any, Callable, Iterable, Sequence, Type
55

66
from rodi import ContainerProtocol
77

@@ -46,7 +46,7 @@ class RolesRequirement(Requirement):
4646

4747
__slots__ = ("_roles",)
4848

49-
def __init__(self, roles: Optional[Sequence[str]] = None):
49+
def __init__(self, roles: Sequence[str] | None = None):
5050
self._roles = list(roles) if roles else None
5151

5252
def handle(self, context: "AuthorizationContext"):
@@ -61,7 +61,7 @@ def handle(self, context: "AuthorizationContext"):
6161
context.succeed(self)
6262

6363

64-
RequirementConfType = Union[Requirement, Type[Requirement]]
64+
RequirementConfType = Requirement | Type[Requirement]
6565

6666

6767
@lru_cache(maxsize=None)
@@ -79,11 +79,11 @@ class UnauthorizedError(AuthorizationError):
7979

8080
def __init__(
8181
self,
82-
forced_failure: Optional[str],
82+
forced_failure: str | None,
8383
failed_requirements: Sequence[Requirement],
84-
scheme: Optional[str] = None,
85-
error: Optional[str] = None,
86-
error_description: Optional[str] = None,
84+
scheme: str | None = None,
85+
error: str | None = None,
86+
error_description: str | None = None,
8787
):
8888
"""
8989
Creates a new instance of UnauthorizedError, with details.
@@ -132,11 +132,11 @@ class AuthorizationContext:
132132
def __init__(self, identity: Identity, requirements: Sequence[Requirement]):
133133
self.identity = identity
134134
self.requirements = requirements
135-
self._succeeded: Set[Requirement] = set()
136-
self._failed_forced: Optional[str] = None
135+
self._succeeded: set[Requirement] = set()
136+
self._failed_forced: str | None = None
137137

138138
@property
139-
def pending_requirements(self) -> List[Requirement]:
139+
def pending_requirements(self) -> list[Requirement]:
140140
return [item for item in self.requirements if item not in self._succeeded]
141141

142142
@property
@@ -146,7 +146,7 @@ def has_succeeded(self) -> bool:
146146
return all(requirement in self._succeeded for requirement in self.requirements)
147147

148148
@property
149-
def forced_failure(self) -> Optional[str]:
149+
def forced_failure(self) -> str | None:
150150
return None if self._failed_forced is None else str(self._failed_forced)
151151

152152
def fail(self, reason: str):
@@ -208,16 +208,16 @@ class AuthorizationStrategy(BaseStrategy):
208208
def __init__(
209209
self,
210210
*policies: Policy,
211-
container: Optional[ContainerProtocol] = None,
212-
default_policy: Optional[Policy] = None,
213-
identity_getter: Optional[Callable[..., Identity]] = None,
211+
container: ContainerProtocol | None = None,
212+
default_policy: Policy | None = None,
213+
identity_getter: Callable[..., Identity] | None = None,
214214
):
215215
super().__init__(container)
216216
self.policies = list(policies)
217217
self.default_policy = default_policy
218218
self.identity_getter = identity_getter
219219

220-
def get_policy(self, name: str) -> Optional[Policy]:
220+
def get_policy(self, name: str) -> Policy | None:
221221
for policy in self.policies:
222222
if policy.name == name:
223223
return policy
@@ -237,10 +237,10 @@ def with_default_policy(self, policy: Policy) -> "AuthorizationStrategy":
237237

238238
async def authorize(
239239
self,
240-
policy_name: Optional[str],
240+
policy_name: str | None,
241241
identity: Identity,
242242
scope: Any = None,
243-
roles: Optional[Sequence[str]] = None,
243+
roles: Sequence[str] | None = None,
244244
):
245245
if policy_name:
246246
policy = self.get_policy(policy_name)
@@ -268,7 +268,7 @@ async def authorize(
268268
raise UnauthorizedError("The resource requires authentication", [])
269269

270270
def _get_requirements(
271-
self, policy: Policy, scope: Any, roles: Optional[Sequence[str]] = None
271+
self, policy: Policy, scope: Any, roles: Sequence[str] | None = None
272272
) -> Iterable[Requirement]:
273273
if roles:
274274
yield RolesRequirement(roles=roles)
@@ -279,15 +279,15 @@ async def _handle_with_policy(
279279
policy: Policy,
280280
identity: Identity,
281281
scope: Any,
282-
roles: Optional[Sequence[str]] = None,
282+
roles: Sequence[str] | None = None,
283283
):
284284
with AuthorizationContext(
285285
identity, list(self._get_requirements(policy, scope, roles))
286286
) as context:
287287
await self._handle_context(identity, context)
288288

289289
async def _handle_with_roles(
290-
self, identity: Identity, roles: Optional[Sequence[str]] = None
290+
self, identity: Identity, roles: Sequence[str] | None = None
291291
):
292292
# This method is to be used only when the user specified roles without a policy
293293
with AuthorizationContext(identity, [RolesRequirement(roles=roles)]) as context:
@@ -310,13 +310,13 @@ async def _handle_context(self, identity: Identity, context: AuthorizationContex
310310
)
311311

312312
async def _handle_with_identity_getter(
313-
self, policy_name: Optional[str], *args, **kwargs
313+
self, policy_name: str | None, *args, **kwargs
314314
):
315315
if self.identity_getter is None:
316316
raise TypeError("Missing identity getter function.")
317317
await self.authorize(policy_name, self.identity_getter(*args, **kwargs))
318318

319-
def __call__(self, policy: Optional[str] = None):
319+
def __call__(self, policy: str | None = None):
320320
"""
321321
Decorates a function to apply authorization logic on each call.
322322
"""

guardpost/common.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from collections.abc import Mapping
22
from typing import Mapping as MappingType
3-
from typing import Sequence, Union
3+
from typing import Sequence
44

55
from .authorization import AuthorizationContext, Policy, Requirement
66

@@ -35,7 +35,7 @@ def handle(self, context: AuthorizationContext):
3535
context.succeed(self)
3636

3737

38-
RequiredClaimsType = Union[MappingType[str, str], Sequence[str], str]
38+
RequiredClaimsType = MappingType[str, str] | Sequence[str] | str
3939

4040

4141
class ClaimsRequirement(Requirement):

guardpost/jwks/__init__.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from abc import ABC, abstractmethod
33
from dataclasses import dataclass
44
from enum import Enum
5-
from typing import Dict, List, Optional, Type
5+
from typing import Type
66

77
from cryptography.hazmat.backends import default_backend
88
from cryptography.hazmat.primitives import serialization
@@ -24,7 +24,7 @@ def _raise_if_missing(value: dict, *keys: str) -> None:
2424
raise ValueError(f"Missing {key}")
2525

2626

27-
_EC_CURVES: Dict[str, Type[EllipticCurve]] = {
27+
_EC_CURVES: dict[str, Type[EllipticCurve]] = {
2828
"P-256": SECP256R1,
2929
"P-384": SECP384R1,
3030
"P-521": SECP521R1,
@@ -62,14 +62,14 @@ class JWK:
6262

6363
kty: KeyType
6464
pem: bytes
65-
kid: Optional[str] = None
65+
kid: str | None = None
6666
# RSA parameters
67-
n: Optional[str] = None
68-
e: Optional[str] = None
67+
n: str | None = None
68+
e: str | None = None
6969
# EC parameters
70-
crv: Optional[str] = None
71-
x: Optional[str] = None
72-
y: Optional[str] = None
70+
crv: str | None = None
71+
x: str | None = None
72+
y: str | None = None
7373

7474
@classmethod
7575
def from_dict(cls, value) -> "JWK":
@@ -104,7 +104,7 @@ def from_dict(cls, value) -> "JWK":
104104

105105
@dataclass
106106
class JWKS:
107-
keys: List[JWK]
107+
keys: list[JWK]
108108

109109
def update(self, new_set: "JWKS"):
110110
self.keys = list({key.kid: key for key in self.keys + new_set.keys}.values())

guardpost/jwks/caching.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import time
2-
from typing import Optional
32

43
from . import JWK, JWKS, KeysProvider
54

@@ -24,7 +23,7 @@ def __init__(
2423
if not keys_provider:
2524
raise TypeError("Missing KeysProvider")
2625

27-
self._keys: Optional[JWKS] = None
26+
self._keys: JWKS | None = None
2827
self._cache_time = cache_time
2928
self._refresh_time = refresh_time
3029
self._last_fetch_time: float = 0
@@ -57,7 +56,7 @@ async def get_keys(self) -> JWKS:
5756
return self._keys
5857
return await self._fetch_keys()
5958

60-
async def get_key(self, kid: str) -> Optional[JWK]:
59+
async def get_key(self, kid: str) -> JWK | None:
6160
"""
6261
Tries to get a JWK by kid. If the JWK is not found and the last time the keys
6362
were fetched is older than `refresh_time` (default 120 seconds), it fetches

0 commit comments

Comments
 (0)