Skip to content

Commit e28bfe3

Browse files
committed
Add model schema caching for improved performance
1 parent e9cc3aa commit e28bfe3

File tree

1 file changed

+34
-15
lines changed

1 file changed

+34
-15
lines changed

fastopenapi/base_router.py

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import typing
44
from collections.abc import Callable
55
from http import HTTPStatus
6-
from typing import Any
6+
from typing import Any, Dict, ClassVar
77

88
from pydantic import BaseModel
99

@@ -43,6 +43,8 @@ class BaseRouter:
4343
It can include sub-routers and generate an OpenAPI specification from
4444
the declared routes.
4545
"""
46+
# Class-level cache for model schemas to avoid redundant processing
47+
_model_schema_cache: ClassVar[Dict[str, dict]] = {}
4648

4749
def __init__(
4850
self,
@@ -140,7 +142,7 @@ def generate_openapi(self) -> dict:
140142
}
141143
definitions = {}
142144
for path, method, endpoint in self._routes:
143-
openapi_path = re.sub(r"<(?:w:)?(\w+)>", r"{\1}", path)
145+
openapi_path = re.sub(r"<(?:\w:)?(\w+)>", r"{\1}", path)
144146
operation = self._build_operation(
145147
endpoint, definitions, openapi_path, method
146148
)
@@ -259,18 +261,35 @@ def _serialize_response(result: Any) -> Any:
259261
return {k: BaseRouter._serialize_response(v) for k, v in result.items()}
260262
return result
261263

262-
@staticmethod
263-
def _get_model_schema(model: type[BaseModel], definitions: dict) -> dict:
264-
model_schema = model.model_json_schema(
265-
ref_template="#/components/schemas/{model}"
266-
)
267-
for key in ("definitions", "$defs"):
268-
if key in model_schema:
269-
definitions.update(model_schema[key])
270-
del model_schema[key]
271-
if model.__name__ not in definitions:
272-
definitions[model.__name__] = model_schema
273-
return {"$ref": f"#/components/schemas/{model.__name__}"}
264+
@classmethod
265+
def _get_model_schema(cls, model: type[BaseModel], definitions: dict) -> dict:
266+
"""
267+
Get the OpenAPI schema for a Pydantic model, with caching for better performance.
268+
"""
269+
model_name = model.__name__
270+
cache_key = f"{model.__module__}.{model_name}"
271+
272+
# Check if the schema is already in the class-level cache
273+
if cache_key not in cls._model_schema_cache:
274+
# Generate the schema if it's not in the cache
275+
model_schema = model.model_json_schema(
276+
ref_template="#/components/schemas/{model}"
277+
)
278+
279+
# Process and store nested definitions
280+
for key in ("definitions", "$defs"):
281+
if key in model_schema:
282+
definitions.update(model_schema[key])
283+
del model_schema[key]
284+
285+
# Add schema to the cache
286+
cls._model_schema_cache[cache_key] = model_schema
287+
288+
# Make sure the schema is in the definitions dictionary
289+
if model_name not in definitions:
290+
definitions[model_name] = cls._model_schema_cache[cache_key]
291+
292+
return {"$ref": f"#/components/schemas/{model_name}"}
274293

275294
@staticmethod
276295
def render_swagger_ui(openapi_json_url: str) -> str:
@@ -351,4 +370,4 @@ def resolve_endpoint_params(
351370
def openapi(self) -> dict:
352371
if self._openapi_schema is None:
353372
self._openapi_schema = self.generate_openapi()
354-
return self._openapi_schema
373+
return self._openapi_schema

0 commit comments

Comments
 (0)