Skip to content

Commit f93042a

Browse files
authored
Add UseAdditionalFields customization (#1180)
1 parent f4c4162 commit f93042a

File tree

4 files changed

+65
-10
lines changed

4 files changed

+65
-10
lines changed

docs/tutorials_advanced/customization.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Now we can customize our page.
3030
If you want to change default name of page class, you should use `UseName` customizer:
3131

3232
```py
33-
{!../docs_src/tutorials_advanced/customization.py [ln:17-21]!}
33+
{!../docs_src/tutorials_advanced/customization.py [ln:18-22]!}
3434
```
3535

3636
1. Now your class will be names 'IntPage' instead of 'CustomizedPage'.
@@ -47,7 +47,7 @@ By default, cursor-based page don't include total count of items, and offset-bas
4747
If you want to change this behavior, you should use `UseIncludeTotal` customizer:
4848

4949
```py
50-
{!../docs_src/tutorials_advanced/customization.py [ln:22-26]!}
50+
{!../docs_src/tutorials_advanced/customization.py [ln:23-27]!}
5151
```
5252

5353
1. Now when you will paginate using `PageNoTotal` class, it will not include total count of items.
@@ -57,7 +57,7 @@ If you want to change this behavior, you should use `UseIncludeTotal` customizer
5757
If you want to change default values of pagination parameters, you should use `UseParamsFields` customizer:
5858

5959
```py
60-
{!../docs_src/tutorials_advanced/customization.py [ln:27-31]!}
60+
{!../docs_src/tutorials_advanced/customization.py [ln:28-32]!}
6161
```
6262

6363
1. Now when `size` parameter is not provided, it will be equal to 500.
@@ -68,7 +68,7 @@ If you want to change default values of pagination parameters, you should use `U
6868
If you want to change type of pagination parameters, you should use `UseParams` customizer:
6969

7070
```py
71-
{!../docs_src/tutorials_advanced/customization.py [ln:32-36]!}
71+
{!../docs_src/tutorials_advanced/customization.py [ln:33-37]!}
7272
```
7373

7474
1. Now all pagination parameters will be optional.
@@ -79,7 +79,7 @@ If you want to change type of pagination parameters, you should use `UseParams`
7979
If you want use another name of field rather than default, you should use `UseFieldsAliases` customizer:
8080

8181
```py
82-
{!../docs_src/tutorials_advanced/customization.py [ln:46-50]!}
82+
{!../docs_src/tutorials_advanced/customization.py [ln:47-51]!}
8383
```
8484

8585
1. Now `total` field will be serialized as `count`.
@@ -90,7 +90,7 @@ If you want use another name of field rather than default, you should use `UseFi
9090
If you want to exclude some fields from serialization, you should use `UseExcludedFields` customizer:
9191

9292
```py
93-
{!../docs_src/tutorials_advanced/customization.py [ln:51-55]!}
93+
{!../docs_src/tutorials_advanced/customization.py [ln:52-56]!}
9494
```
9595

9696
1. Now `total` field will not be serialized.
@@ -101,7 +101,7 @@ If you want to exclude some fields from serialization, you should use `UseExclud
101101
If you want to change pydantic model config, you should use `UseModelConfig` customizer:
102102

103103
```py
104-
{!../docs_src/tutorials_advanced/customization.py [ln:56-60]!}
104+
{!../docs_src/tutorials_advanced/customization.py [ln:57-61]!}
105105
```
106106

107107
1. Now `Page` class will have `anystr_lower` set to `True`.
@@ -112,7 +112,7 @@ If you want to change pydantic model config, you should use `UseModelConfig` cus
112112
If you want to change type of page parameters, you should use `UseParams` customizer:
113113

114114
```py
115-
{!../docs_src/tutorials_advanced/customization.py [ln:38-46]!}
115+
{!../docs_src/tutorials_advanced/customization.py [ln:39-47]!}
116116
```
117117

118118
1. Now `Page.__params_type__` attribute will be point to `MyParams` class.
@@ -123,7 +123,7 @@ If you want to change type of page parameters, you should use `UseParams` custom
123123
You can use multiple customizers at once, just pass them as to regular `Annotated`:
124124

125125
```py
126-
{!../docs_src/tutorials_advanced/customization.py [ln:62-68]!}
126+
{!../docs_src/tutorials_advanced/customization.py [ln:63-69]!}
127127
```
128128

129129
1. Now `CustomPage` will have `CustomPage` name, no total count of items, all params optional.

docs_src/tutorials_advanced/customization.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
from typing import TypeVar
1+
from typing import Optional, TypeVar
22

33
from fastapi_pagination import Page, Params
44
from fastapi_pagination.customization import (
55
CustomizedPage,
6+
UseAdditionalFields,
67
UseExcludedFields,
78
UseFieldsAliases,
89
UseIncludeTotal,
@@ -65,3 +66,12 @@ class MyParams(Params): ... # your magic here
6566
UseIncludeTotal(False),
6667
UseOptionalParams(),
6768
] # (1)
69+
70+
71+
PageWithAdditionalFields = CustomizedPage[
72+
Page[T],
73+
UseAdditionalFields(
74+
filters=str, # without default value
75+
sort=(Optional[str], None), # with default value
76+
),
77+
] # (1)

fastapi_pagination/customization.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"UseModelConfig",
1515
"UseExcludedFields",
1616
"UseFieldsAliases",
17+
"UseAdditionalFields",
1718
"ClsNamespace",
1819
"PageCls",
1920
]
@@ -32,6 +33,7 @@
3233
Tuple,
3334
Type,
3435
TypeVar,
36+
Union,
3537
no_type_check,
3638
runtime_checkable,
3739
)
@@ -321,3 +323,20 @@ def customize_page_ns(self, page_cls: PageCls, ns: ClsNamespace) -> None:
321323
for name, alias in self.aliases.items():
322324
assert name in page_cls.__fields__, f"Unknown field {name!r}"
323325
fields_config[name]["alias"] = alias
326+
327+
328+
_RawFieldDef: TypeAlias = Union[Any, Tuple[Any, Any]]
329+
330+
331+
class UseAdditionalFields(PageCustomizer):
332+
def __init__(self, **fields: _RawFieldDef) -> None:
333+
self.fields = fields
334+
335+
def customize_page_ns(self, page_cls: PageCls, ns: ClsNamespace) -> None:
336+
anns = ns.setdefault("__annotations__", {})
337+
338+
for name, field in self.fields.items():
339+
if isinstance(field, tuple):
340+
anns[name], ns[name] = field
341+
else:
342+
anns[name] = field

tests/test_customization.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
ClsNamespace,
1010
CustomizedPage,
1111
PageCustomizer,
12+
UseAdditionalFields,
1213
UseExcludedFields,
1314
UseFieldsAliases,
1415
UseIncludeTotal,
@@ -201,3 +202,28 @@ def test_use_aliases():
201202
assert CustomPage.model_fields["total"].serialization_alias == "count"
202203
else:
203204
assert CustomPage.__fields__["total"].alias == "count"
205+
206+
207+
def test_additional_fields():
208+
CustomPage = CustomizedPage[
209+
Page,
210+
UseAdditionalFields(
211+
a=int,
212+
b=(str, "my-default"),
213+
),
214+
]
215+
216+
if IS_PYDANTIC_V2:
217+
from pydantic_core import PydanticUndefined
218+
219+
assert CustomPage.model_fields["a"].annotation == int
220+
assert CustomPage.model_fields["a"].default is PydanticUndefined
221+
222+
assert CustomPage.model_fields["b"].annotation == str
223+
assert CustomPage.model_fields["b"].default == "my-default"
224+
else:
225+
assert CustomPage.__fields__["a"].type_ == int
226+
assert CustomPage.__fields__["a"].default is None
227+
228+
assert CustomPage.__fields__["b"].type_ == str
229+
assert CustomPage.__fields__["b"].default == "my-default"

0 commit comments

Comments
 (0)