Coverage for polar/auth/routing.py: 100%
32 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 12:42 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 12:42 +0000
1import inspect
2import typing
3from collections.abc import Callable
5from fastapi.params import Depends
6from fastapi.routing import APIRoute
8from polar.auth.dependencies import _Authenticator
9from polar.auth.scope import RESERVED_SCOPES
12class DocumentedAuthSubjectAPIRoute(APIRoute):
13 """
14 A subclass of `APIRoute` that automatically
15 documents the allowed subjects and scopes for the endpoint.
16 """
18 def __init__(
19 self, path: str, endpoint: Callable[..., typing.Any], **kwargs: typing.Any
20 ) -> None:
21 openapi_extra = kwargs.get("openapi_extra") or {}
22 # Check we haven't already added the allowed subjects
23 if "x-polar-allowed-subjects" not in openapi_extra:
24 for param in typing.get_type_hints(endpoint, include_extras=True).values():
25 if typing.get_origin(param) is not typing.Annotated:
26 continue
28 metadata = param.__metadata__
29 if len(metadata) == 0 or not isinstance(metadata[0], Depends):
30 continue
32 dependency = metadata[0].dependency
33 if not isinstance(dependency, _Authenticator):
34 continue
36 allowed_subjects = dependency.allowed_subjects
37 required_scopes = dependency.required_scopes
39 allowed_subjects_names = sorted(
40 [allowed_subject.__name__ for allowed_subject in allowed_subjects]
41 )
43 kwargs["openapi_extra"] = {
44 "x-polar-allowed-subjects": allowed_subjects_names,
45 **openapi_extra,
46 }
48 description = kwargs["description"] or inspect.cleandoc(
49 endpoint.__doc__ or ""
50 )
51 scopes_list = [
52 f"`{s}`"
53 for s in sorted(required_scopes or [])
54 if s not in RESERVED_SCOPES
55 ]
56 if scopes_list:
57 description += f"\n\n**Scopes**: {' '.join(scopes_list)}"
58 kwargs["description"] = description
60 break
62 super().__init__(path, endpoint, **kwargs)
65__all__ = ["DocumentedAuthSubjectAPIRoute"]