Skip to content

Commit 8a7dca2

Browse files
committed
Added use_aliases
1 parent 9f55f4c commit 8a7dca2

File tree

6 files changed

+68
-22
lines changed

6 files changed

+68
-22
lines changed

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ body:
1515
attributes:
1616
label: Environment
1717
description: Describe your environment (Python version, OS, FastOpenAPI version, framework used, etc.)
18-
placeholder: Python 3.11, FastOpenAPI 0.5.0, Ubuntu 22.04, Flask 2.2
18+
placeholder: Python 3.11, FastOpenAPI 0.6.0, Ubuntu 22.04, Flask 2.2
1919
validations:
2020
required: true
2121

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ All notable changes to FastOpenAPI are documented in this file.
44

55
FastOpenAPI follows the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format.
66

7+
## [0.6.0] – 2025‑04‑16
8+
9+
### Added
10+
- The `use_aliases` parameter was added to the `BaseRouter` constructor. Default is `True`. To preserve the previous behavior (without using aliases from Pydantic), set `use_aliases=False`.
11+
12+
### Changed
13+
- The `_serialize_response method` is now an instance method (was a `@staticmethod`) — to support `use_aliases`.
14+
- The `_get_model_schema` method was temporarily changed from a `@classmethod` to a regular method — for consistent behavior with `use_aliases`.
15+
16+
### Deprecated
17+
- `use_aliases` is deprecated and will be removed in version 0.7.0.
18+
719
## [0.5.0] - 2025-04-13
820

921
### Added

docs/en/docs/changelog.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33
All notable changes to FastOpenAPI will be documented in this file.
44

5+
## [0.6.0] – 2025‑04‑16
6+
7+
### Added
8+
- The `use_aliases` parameter was added to the `BaseRouter` constructor. Default is `True`. To preserve the previous behavior (without using aliases from Pydantic), set `use_aliases=False`.
9+
10+
### Changed
11+
- The `_serialize_response method` is now an instance method (was a `@staticmethod`) — to support `use_aliases`.
12+
- The `_get_model_schema` method was temporarily changed from a `@classmethod` to a regular method — for consistent behavior with `use_aliases`.
13+
14+
### Deprecated
15+
- `use_aliases` is deprecated and will be removed in version 0.7.0.
16+
17+
518
## [0.5.0] - 2025-04-13
619

720
### Added

fastopenapi/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.5.0"
1+
__version__ = "0.6.0"

fastopenapi/base_router.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import inspect
22
import re
33
import typing
4+
import warnings
45
from collections.abc import Callable
56
from http import HTTPStatus
67
from typing import Any, ClassVar
@@ -43,6 +44,7 @@ class BaseRouter:
4344
- title: Title of the API documentation (defaults to "My App").
4445
- version: Version of the API (defaults to "0.1.0").
4546
- description: Description of the API
47+
- use_aliases: Temporary argument to maintain backward compatibility
4648
(included in OpenAPI info, default "API documentation").
4749
4850
The BaseRouter allows defining routes using decorator methods (get, post, etc.).
@@ -63,6 +65,7 @@ def __init__(
6365
title: str = "My App",
6466
version: str = "0.1.0",
6567
description: str = "API documentation",
68+
use_aliases: bool = True,
6669
):
6770
self.app = app
6871
self.docs_url = docs_url
@@ -74,6 +77,15 @@ def __init__(
7477
self.description = description
7578
self._routes: list[tuple[str, str, Callable]] = []
7679
self._openapi_schema = None
80+
self.use_aliases = use_aliases
81+
# TODO Remove use_aliases in 0.7.0
82+
if not use_aliases:
83+
warnings.warn(
84+
"Setting use_aliases=False is deprecated. "
85+
"It will be removed in version 0.7.0",
86+
FutureWarning,
87+
stacklevel=2,
88+
)
7789
if self.app is not None:
7890
if self.docs_url and self.redoc_url and self.openapi_url:
7991
self._register_docs_endpoints()
@@ -225,8 +237,9 @@ def _build_parameters_and_body(
225237
param.annotation, BaseModel
226238
):
227239
if http_method.upper() == "GET":
240+
# TODO Remove use_aliases in 0.7.0
228241
model_schema = param.annotation.model_json_schema(
229-
mode="serialization"
242+
mode="serialization" if self.use_aliases else "validation"
230243
)
231244
required_fields = model_schema.get("required", [])
232245
properties = model_schema.get("properties", {})
@@ -334,31 +347,32 @@ def _register_docs_endpoints(self):
334347
"""
335348
raise NotImplementedError
336349

337-
@staticmethod
338-
def _serialize_response(result: Any) -> Any:
350+
def _serialize_response(self, result: Any) -> Any:
339351
from pydantic import BaseModel
340352

341353
if isinstance(result, BaseModel):
342-
return result.model_dump(by_alias=True)
354+
# TODO Remove use_aliases in 0.7.0
355+
return result.model_dump(by_alias=self.use_aliases)
343356
if isinstance(result, list):
344-
return [BaseRouter._serialize_response(item) for item in result]
357+
return [self._serialize_response(item) for item in result]
345358
if isinstance(result, dict):
346-
return {k: BaseRouter._serialize_response(v) for k, v in result.items()}
359+
return {k: self._serialize_response(v) for k, v in result.items()}
347360
return result
348361

349-
@classmethod
350-
def _get_model_schema(cls, model: type[BaseModel], definitions: dict) -> dict:
362+
def _get_model_schema(self, model: type[BaseModel], definitions: dict) -> dict:
351363
"""
352364
Get the OpenAPI schema for a Pydantic model, with caching for better performance
353365
"""
354366
model_name = model.__name__
355367
cache_key = f"{model.__module__}.{model_name}"
356368

357369
# Check if the schema is already in the class-level cache
358-
if cache_key not in cls._model_schema_cache:
370+
if cache_key not in self._model_schema_cache:
359371
# Generate the schema if it's not in the cache
372+
# TODO Remove use_aliases in 0.7.0
360373
model_schema = model.model_json_schema(
361-
mode="serialization", ref_template="#/components/schemas/{model}"
374+
mode="serialization" if self.use_aliases else "validation",
375+
ref_template="#/components/schemas/{model}",
362376
)
363377

364378
# Process and store nested definitions
@@ -368,11 +382,11 @@ def _get_model_schema(cls, model: type[BaseModel], definitions: dict) -> dict:
368382
del model_schema[key]
369383

370384
# Add schema to the cache
371-
cls._model_schema_cache[cache_key] = model_schema
385+
self._model_schema_cache[cache_key] = model_schema
372386

373387
# Make sure the schema is in the definitions dictionary
374388
if model_name not in definitions:
375-
definitions[model_name] = cls._model_schema_cache[cache_key]
389+
definitions[model_name] = self._model_schema_cache[cache_key]
376390

377391
return {"$ref": f"#/components/schemas/{model_name}"}
378392

tests/test_base_router.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ def test_init_without_app(self):
7676
assert self.router_no_app._routes == []
7777
assert self.router_no_app._openapi_schema is None
7878

79+
def test_use_aliases_deprecation_warning(self):
80+
with pytest.warns(FutureWarning) as record:
81+
BaseRouter(use_aliases=False)
82+
msg = str(record[0].message)
83+
assert "will be removed in version 0.7.0" in msg
84+
7985
def test_add_route(self):
8086
# Test adding a route to the router
8187
def test_endpoint():
@@ -420,7 +426,7 @@ def new_endpoint():
420426
def test_serialize_response_with_pydantic_model(self):
421427
# Test serializing a Pydantic model response
422428
model = ResponseModel(id=1, message="test")
423-
result = BaseRouter._serialize_response(model)
429+
result = BaseRouter()._serialize_response(model)
424430

425431
assert isinstance(result, dict)
426432
assert result["id"] == 1
@@ -432,7 +438,7 @@ def test_serialize_response_with_list(self):
432438
ResponseModel(id=1, message="test1"),
433439
ResponseModel(id=2, message="test2"),
434440
]
435-
result = BaseRouter._serialize_response(models)
441+
result = BaseRouter()._serialize_response(models)
436442

437443
assert isinstance(result, list)
438444
assert len(result) == 2
@@ -445,7 +451,7 @@ def test_serialize_response_with_dict(self):
445451
"item1": ResponseModel(id=1, message="test1"),
446452
"item2": ResponseModel(id=2, message="test2"),
447453
}
448-
result = BaseRouter._serialize_response(data)
454+
result = BaseRouter()._serialize_response(data)
449455

450456
assert isinstance(result, dict)
451457
assert len(result) == 2
@@ -454,14 +460,15 @@ def test_serialize_response_with_dict(self):
454460

455461
def test_serialize_response_with_primitive(self):
456462
# Test serializing primitive values
457-
assert BaseRouter._serialize_response(5) == 5
458-
assert BaseRouter._serialize_response("test") == "test"
459-
assert BaseRouter._serialize_response(True) is True
463+
router = BaseRouter()
464+
assert router._serialize_response(5) == 5
465+
assert router._serialize_response("test") == "test"
466+
assert router._serialize_response(True) is True
460467

461468
def test_get_model_schema(self):
462469
# Test getting Pydantic model schema
463470
definitions = {}
464-
schema = BaseRouter._get_model_schema(TestModel, definitions)
471+
schema = BaseRouter()._get_model_schema(TestModel, definitions)
465472

466473
assert "$ref" in schema
467474
assert schema["$ref"] == "#/components/schemas/TestModel"
@@ -473,7 +480,7 @@ def test_get_model_schema(self):
473480
def test_get_model_schema_with_nested_models(self):
474481
# Test getting schema for models with nested models
475482
definitions = {}
476-
schema = BaseRouter._get_model_schema(NestedModel, definitions)
483+
schema = BaseRouter()._get_model_schema(NestedModel, definitions)
477484

478485
assert "$ref" in schema
479486
assert schema["$ref"] == "#/components/schemas/NestedModel"

0 commit comments

Comments
 (0)