|
2 | 2 |
|
3 | 3 | from drf_spectacular import generators, openapi |
4 | 4 | from drf_spectacular.extensions import ( |
| 5 | + OpenApiAuthenticationExtension, |
5 | 6 | OpenApiSerializerExtension, |
6 | 7 | ) |
7 | 8 | from drf_spectacular.plumbing import ResolvedComponent, safe_ref |
8 | 9 | from drf_spectacular.plumbing import append_meta as append_meta_orig |
9 | | -from pydantic import BaseModel |
| 10 | +from pydantic import TypeAdapter |
10 | 11 | from rest_framework.request import Request |
| 12 | +from typing_extensions import is_typeddict |
11 | 13 |
|
12 | 14 |
|
13 | 15 | def append_meta(schema: dict[str, Any], meta: dict[str, Any]) -> dict[str, Any]: |
@@ -135,48 +137,79 @@ def _update_security_for_mcp(self, schema: dict[str, Any]) -> dict[str, Any]: |
135 | 137 | return schema |
136 | 138 |
|
137 | 139 |
|
138 | | -class PydanticSchemaExtension( |
139 | | - OpenApiSerializerExtension # type: ignore[no-untyped-call] |
140 | | -): |
| 140 | +class TypedDictSchemaExtension(OpenApiSerializerExtension): |
141 | 141 | """ |
142 | 142 | An OpenAPI extension that allows drf-spectacular to generate schema documentation |
143 | | - from Pydantic models. |
| 143 | + from TypedDicts via Pydantic. |
144 | 144 |
|
145 | | - This extension is automatically used when a Pydantic BaseModel subclass is passed |
| 145 | + This extension is automatically used when a TypedDict subclass is passed |
146 | 146 | as a response type in @extend_schema decorators. |
147 | 147 | """ |
148 | 148 |
|
149 | | - target_class = "pydantic.BaseModel" |
150 | | - match_subclasses = True |
| 149 | + @classmethod |
| 150 | + def _matches(cls, target: type[Any]) -> bool: |
| 151 | + return is_typeddict(target) |
151 | 152 |
|
152 | 153 | def get_name( |
153 | 154 | self, |
154 | 155 | auto_schema: openapi.AutoSchema | None = None, |
155 | 156 | direction: Literal["request", "response"] | None = None, |
156 | | - ) -> str | None: |
157 | | - return self.target.__name__ # type: ignore[no-any-return] |
| 157 | + ) -> str: |
| 158 | + name: str = self.target.__name__ |
| 159 | + return name |
158 | 160 |
|
159 | 161 | def map_serializer( |
160 | 162 | self, |
161 | 163 | auto_schema: openapi.AutoSchema, |
162 | 164 | direction: str, |
163 | 165 | ) -> dict[str, Any]: |
164 | | - model_cls: type[BaseModel] = self.target |
165 | | - |
166 | | - model_json_schema = model_cls.model_json_schema( |
| 166 | + model_json_schema = TypeAdapter(self.target).json_schema( |
167 | 167 | mode="serialization", |
168 | | - ref_template="#/components/schemas/{model}", |
| 168 | + ref_template="#/components/schemas/%s{model}" % self.get_name(), |
169 | 169 | ) |
170 | 170 |
|
171 | 171 | # Register nested definitions as components |
172 | 172 | if "$defs" in model_json_schema: |
173 | 173 | for ref_name, schema_kwargs in model_json_schema.pop("$defs").items(): |
174 | 174 | component = ResolvedComponent( # type: ignore[no-untyped-call] |
175 | | - name=ref_name, |
| 175 | + name=self.get_name() + ref_name, |
176 | 176 | type=ResolvedComponent.SCHEMA, |
177 | 177 | object=ref_name, |
178 | 178 | schema=schema_kwargs, |
179 | 179 | ) |
180 | 180 | auto_schema.registry.register_on_missing(component) |
181 | 181 |
|
182 | 182 | return model_json_schema |
| 183 | + |
| 184 | + |
| 185 | +class EnvironmentKeyAuthenticationExtension(OpenApiAuthenticationExtension): # type: ignore[no-untyped-call] |
| 186 | + target_class = "environments.authentication.EnvironmentKeyAuthentication" |
| 187 | + name = "Environment API Key" |
| 188 | + |
| 189 | + def get_security_definition( |
| 190 | + self, auto_schema: openapi.AutoSchema | None = None |
| 191 | + ) -> dict[str, Any]: |
| 192 | + return { |
| 193 | + "type": "apiKey", |
| 194 | + "in": "header", |
| 195 | + "name": "X-Environment-Key", |
| 196 | + "description": "For SDK endpoints. <a href='https://docs.flagsmith.com/clients/rest#public-api-endpoints'>Find out more</a>.", |
| 197 | + } |
| 198 | + |
| 199 | + |
| 200 | +class MasterAPIKeyAuthenticationExtension(OpenApiAuthenticationExtension): # type: ignore[no-untyped-call] |
| 201 | + target_class = "api_keys.authentication.MasterAPIKeyAuthentication" |
| 202 | + name = "Master API Key" |
| 203 | + |
| 204 | + def get_security_definition( |
| 205 | + self, auto_schema: openapi.AutoSchema | None = None |
| 206 | + ) -> dict[str, Any]: |
| 207 | + return { |
| 208 | + "type": "apiKey", |
| 209 | + "in": "header", |
| 210 | + "name": "Authorization", |
| 211 | + "description": ( |
| 212 | + "For Admin API endpoints. " |
| 213 | + "<a href='https://docs.flagsmith.com/clients/rest#private-api-endpoints'>Find out more</a>." |
| 214 | + ), |
| 215 | + } |
0 commit comments