Skip to content

Commit ed013c1

Browse files
committed
Added a more adaptive pagination response
1 parent 1c86a87 commit ed013c1

File tree

6 files changed

+81
-41
lines changed

6 files changed

+81
-41
lines changed

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[flake8]
22
max-line-length = 88
3-
ignore = E203, E241, E501, W503
3+
ignore = E203, E241, E501, W503, F811
44
exclude =
55
.git,
66
__pycache__

ninja_extra/generic.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from typing import Any, Dict, no_type_check
2+
3+
4+
class GenericModelMeta(type):
5+
registry: Dict = {}
6+
7+
def __getitem__(self, wraps: Any) -> Any:
8+
if (self, wraps) not in self.__class__.registry:
9+
self.__class__.registry[self, wraps] = self().get_generic_type(wraps)
10+
return self.__class__.registry[self, wraps]
11+
12+
13+
@no_type_check
14+
class GenericType(metaclass=GenericModelMeta):
15+
"""
16+
Get a wrapper class that mimic python 3.8 generic support to python3.6, 3.7
17+
Examples
18+
--------
19+
Create a Class to reference the generic model to be create:
20+
>>> class ObjectA(GenericType):
21+
>>> def get_generic_type(self, wrap_type):
22+
>>> class ObjectAGeneric(self):
23+
>>> item: wrap_type
24+
>>> ObjectAGeneric.__name__ = (
25+
>>> f"{self.__class__.__name__}[{str(wrap_type.__name__).capitalize()}]"
26+
>>> )
27+
>>> return ObjectAGeneric
28+
29+
Usage:
30+
>>> class Pass: ...
31+
>>> object_generic_type = ObjectA[Pass]
32+
"""

ninja_extra/operation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ def _log_action(
4343
route_function: "RouteFunction" = (
4444
self.view_func.get_route_function() # type:ignore
4545
)
46+
assert route_function.controller
4647
msg = (
4748
f'"{request.method.upper() if request.method else "METHOD NOT FOUND"} - '
48-
f'{route_function.controller.__class__.__name__}[{self.view_func.__name__}] {request.path}" '
49+
f'{route_function.controller.__name__}[{self.view_func.__name__}] {request.path}" '
4950
f"{duration if duration else str(ex)}"
5051
)
5152
logger(msg, **kwargs)

ninja_extra/pagination.py

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import inspect
22
import logging
3-
import sys
43
from collections import OrderedDict
54
from functools import wraps
6-
from typing import TYPE_CHECKING, Any, Callable, Optional, Type, Union, cast
5+
from typing import TYPE_CHECKING, Any, Callable, Optional, Type, Union, cast, overload
76

87
from django.core.paginator import InvalidPage, Page, Paginator
98
from django.db.models import QuerySet
@@ -17,7 +16,7 @@
1716

1817
from ninja_extra.conf import settings
1918
from ninja_extra.exceptions import NotFound
20-
from ninja_extra.schemas import PaginatedResponseSchema, get_paginated_response_schema
19+
from ninja_extra.schemas import PaginatedResponseSchema
2120
from ninja_extra.urls import remove_query_param, replace_query_param
2221

2322
logger = logging.getLogger()
@@ -31,6 +30,7 @@
3130
"PaginationBase",
3231
"LimitOffsetPagination",
3332
"paginate",
33+
"PaginatedResponseSchema",
3434
]
3535

3636

@@ -106,11 +106,9 @@ def get_paginated_response(self, *, base_url: str, page: Page) -> DictStrAny:
106106

107107
@classmethod
108108
def get_response_schema(
109-
cls, response_schema: Union[Schema, Type[Schema]]
110-
) -> Type[Schema]:
111-
if sys.version_info >= (3, 8):
112-
return PaginatedResponseSchema[response_schema]
113-
return get_paginated_response_schema(response_schema)
109+
cls, response_schema: Union[Schema, Type[Schema], Any]
110+
) -> Any:
111+
return PaginatedResponseSchema[response_schema]
114112

115113
def get_next_link(self, url: str, page: Page) -> Optional[str]:
116114
if not page.has_next():
@@ -136,7 +134,21 @@ def get_page_size(self, page_size: int) -> int:
136134
return self.page_size
137135

138136

139-
def paginate(func_or_pgn_class: Any = NOT_SET, **paginator_params: Any) -> Callable:
137+
@overload
138+
def paginate() -> Callable[..., Any]:
139+
...
140+
141+
142+
@overload
143+
def paginate(
144+
func_or_pgn_class: Any = NOT_SET, **paginator_params: Any
145+
) -> Callable[..., Any]:
146+
...
147+
148+
149+
def paginate(
150+
func_or_pgn_class: Any = NOT_SET, **paginator_params: Any
151+
) -> Callable[..., Any]:
140152
isfunction = inspect.isfunction(func_or_pgn_class)
141153
isnotset = func_or_pgn_class == NOT_SET
142154

@@ -148,32 +160,31 @@ def paginate(func_or_pgn_class: Any = NOT_SET, **paginator_params: Any) -> Calla
148160
if not isnotset:
149161
pagination_class = func_or_pgn_class
150162

151-
def wrapper(func: Callable) -> Any:
163+
def wrapper(func: Callable[..., Any]) -> Any:
152164
return _inject_pagination(func, pagination_class, **paginator_params)
153165

154166
return wrapper
155167

156168

157169
def _inject_pagination(
158-
func: Callable,
170+
func: Callable[..., Any],
159171
paginator_class: Type[PaginationBase],
160172
**paginator_params: Any,
161-
) -> Callable:
162-
func_modified = cast(Any, func)
163-
func_modified.has_kwargs = True
164-
if not has_kwargs(func_modified):
165-
func_modified.has_kwargs = False
173+
) -> Callable[..., Any]:
174+
func.has_kwargs = True # type: ignore
175+
if not has_kwargs(func):
176+
func.has_kwargs = False # type: ignore
166177
logger.debug(
167-
f"function {func_modified.__name__} should have **kwargs if you want to use pagination parameters"
178+
f"function {func.__name__} should have **kwargs if you want to use pagination parameters"
168179
)
169180

170181
paginator: PaginationBase = paginator_class(**paginator_params)
171182
paginator_kwargs_name = "pagination"
172183

173-
@wraps(func_modified)
184+
@wraps(func)
174185
def view_with_pagination(controller: "APIController", *args: Any, **kw: Any) -> Any:
175186
func_kwargs = dict(kw)
176-
if not func_modified.has_kwargs:
187+
if not func.has_kwargs: # type: ignore
177188
func_kwargs.pop(paginator_kwargs_name)
178189

179190
items = func(controller, *args, **func_kwargs)

ninja_extra/schemas/__init__.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
from .response import (
2-
PaginatedResponseSchema,
3-
RouteParameter,
4-
get_paginated_response_schema,
5-
)
1+
from .response import PaginatedResponseSchema, RouteParameter
62

7-
__all__ = ["PaginatedResponseSchema", "get_paginated_response_schema", "RouteParameter"]
3+
__all__ = ["PaginatedResponseSchema", "RouteParameter"]

ninja_extra/schemas/response.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import sys
2-
from typing import Any, Generic, List, Optional, Type, TypeVar, Union
2+
from typing import Any, Generic, List, Optional, Type, TypeVar
33

44
from ninja import Schema
55
from ninja.constants import NOT_SET
66
from pydantic.generics import GenericModel
77
from pydantic.main import BaseModel
88
from pydantic.networks import AnyHttpUrl
99

10-
T = TypeVar("T")
10+
from ninja_extra.generic import GenericType
1111

12-
PaginatedResponseSchema = None
12+
T = TypeVar("T")
1313

1414

1515
class BasePaginatedResponseSchema(Schema):
@@ -19,6 +19,17 @@ class BasePaginatedResponseSchema(Schema):
1919
results: List[Any]
2020

2121

22+
class PaginatedResponseSchema(GenericType):
23+
def get_generic_type(self, wrap_type: Any) -> Type[BasePaginatedResponseSchema]:
24+
class ListResponseSchema(BasePaginatedResponseSchema):
25+
results: List[wrap_type] # type: ignore
26+
27+
ListResponseSchema.__name__ = (
28+
f"{self.__class__.__name__}[{str(wrap_type.__name__).capitalize()}]"
29+
)
30+
return ListResponseSchema
31+
32+
2233
if sys.version_info >= (3, 8):
2334

2435
class PaginatedResponseSchema(
@@ -27,17 +38,6 @@ class PaginatedResponseSchema(
2738
results: List[T]
2839

2940

30-
def get_paginated_response_schema(
31-
item_schema: Union[Schema, Type[Schema]],
32-
) -> Type[BasePaginatedResponseSchema]:
33-
# fix for paginatedResponseSchema for python 3.6 and 3.7 which doesn't support generic typing
34-
class ListResponseSchema(BasePaginatedResponseSchema):
35-
results: List[item_schema] # type: ignore
36-
37-
ListResponseSchema.__name__ = f"List{str(item_schema.__name__).capitalize()}" # type: ignore
38-
return ListResponseSchema
39-
40-
4141
class RouteParameter(BaseModel):
4242
path: str
4343
methods: List[str]

0 commit comments

Comments
 (0)