1- from typing import Any , Dict , List , Optional , Type , Union
1+ import sys
2+ from typing import (
3+ Any ,
4+ Dict ,
5+ Generic ,
6+ Optional ,
7+ Tuple ,
8+ Type ,
9+ TypeVar ,
10+ Union ,
11+ no_type_check ,
12+ )
213
314from ninja import Schema
4- from pydantic .types import UUID1 , UUID3 , UUID4 , UUID5
5-
6- from ninja_extra import status
7-
8-
9- class ControllerResponseMeta (type ):
10- pass
1115
16+ from .. import status
17+ from ..schemas import DetailSchema , IdSchema , OkSchema
18+
19+ T = TypeVar ("T" )
20+ SCHEMA_KEY = "_schema"
21+
22+ if sys .version_info < (3 , 7 ): # pragma: no cover
23+ from typing import GenericMeta
24+
25+ class ControllerResponseMeta (GenericMeta ):
26+ @no_type_check
27+ def __new__ (mcls , name : str , bases : Tuple , namespace : Dict , ** kwargs : Any ):
28+ args = kwargs .get ("args" )
29+ if args and args [0 ].__name__ != "T" :
30+ t = args [0 ]
31+ origin_schema = namespace .get (SCHEMA_KEY )
32+ if origin_schema and hasattr (origin_schema , "__generic_model__" ):
33+ schema = origin_schema .__generic_model__ [t ]
34+ else :
35+ schema = origin_schema [t ]
36+ namespace [SCHEMA_KEY ] = schema
37+ res = super ().__new__ (mcls , name , bases , namespace , ** kwargs )
38+ return res
39+
40+ class GenericControllerResponse (metaclass = ControllerResponseMeta ):
41+ @no_type_check
42+ def __new__ (
43+ cls : Type ["ControllerResponse[T]" ], * args : Any , ** kwargs : Any
44+ ) -> "ControllerResponse[T]" :
45+
46+ if cls ._gorg is Generic or "_schema" not in cls .__dict__ :
47+ raise TypeError (
48+ "Type Generic cannot be instantiated; "
49+ "it can be used only as a base class"
50+ )
51+ return object .__new__ (cls )
52+
53+ else :
54+ _generic_types_cache : Dict [
55+ Tuple [Type [Any ], Union [Any , Tuple [Any , ...]]], Type ["ControllerResponse" ]
56+ ] = {}
57+ GenericControllerResponseT = TypeVar (
58+ "GenericControllerResponseT" , bound = "GenericControllerResponse"
59+ )
60+
61+ class ControllerResponseMeta (type ):
62+ pass
1263
13- class ControllerResponse (metaclass = ControllerResponseMeta ):
64+ class GenericControllerResponse (metaclass = ControllerResponseMeta ):
65+ def __new__ (
66+ cls : Type ["ControllerResponse[T]" ], * args : Any , ** kwargs : Any
67+ ) -> "ControllerResponse[T]" :
68+
69+ if "_schema" not in cls .__dict__ :
70+ raise TypeError (
71+ "Type Generic cannot be instantiated; "
72+ "it can be used only as a base class"
73+ )
74+ return object .__new__ (cls )
75+
76+ @no_type_check
77+ def __class_getitem__ (cls : Type [GenericControllerResponseT ], item : Any ) -> Any :
78+ if isinstance (item , tuple ):
79+ raise TypeError (
80+ "Cannot parameterize a concrete instantiation of a generic model"
81+ )
82+
83+ _key = (cls , item )
84+ _cached_value = _generic_types_cache .get (_key )
85+ if _cached_value :
86+ return _cached_value
87+
88+ if str (type (item )) == "<class 'typing.TypeVar'>" :
89+ result = super ().__class_getitem__ (item )
90+ else :
91+ new_schema = cls .__dict__ .get (SCHEMA_KEY )
92+ if hasattr (new_schema , "__generic_model__" ):
93+ new_schema = new_schema .__generic_model__ [item ]
94+
95+ result = type (
96+ f"{ cls .__name__ } [{ item .__name__ } ]" , (cls ,), {SCHEMA_KEY : new_schema }
97+ )
98+ _generic_types_cache [_key ] = result
99+ return result
100+
101+
102+ class ControllerResponse (GenericControllerResponse , Generic [T ]):
14103 status_code : int = status .HTTP_204_NO_CONTENT
15-
16- def __init__ (self , ** kwargs : Any ) -> None :
17- pass
104+ _schema : Optional [T ]
18105
19106 @classmethod
20- def get_schema (cls ) -> Union [Schema , Type [Schema ], Any ]:
107+ def get_schema (cls ) -> Union [Schema , Type [Schema ], Any ]: # pragma: no cover
21108 raise NotImplementedError
22109
23- def convert_to_schema (self ) -> Any :
110+ def convert_to_schema (self ) -> Any : # pragma: no cover
24111 raise NotImplementedError
25112
26113
27- class Id (ControllerResponse ):
114+ class Id (ControllerResponse [ T ] ):
28115 """
29116 Creates a 201 response with id information
30117 {
31118 id: int| str| UUID4| UUID1| UUID3| UUID5,
32119 }
33120 Example:
34121 Id(423) ==> 201, {id: 423}
122+ OR
123+ Id[int](424) ==> 201, {id: 424}
124+ Id[UUID4]("883a1a3d-7b10-458d-bccc-f9b7219342c9")
125+ ==> 201, {id: "883a1a3d-7b10-458d-bccc-f9b7219342c9"}
35126 """
36127
128+ _schema = IdSchema [Any ] # type: ignore
37129 status_code : int = status .HTTP_201_CREATED
38- id : Union [int , str , UUID4 , UUID1 , UUID3 , UUID5 , Any ]
39130
40- def __init__ (self , id : Any ) -> None :
131+ def __init__ (self , id : T ) -> None :
41132 super (Id , self ).__init__ ()
42133 self .id = id
43134
44- class Id (Schema ):
45- id : Any
46-
47135 def convert_to_schema (self ) -> Any :
48- return self .Id .from_orm (self )
136+ return self ._schema .from_orm (self )
49137
50138 @classmethod
51139 def get_schema (cls ) -> Union [Schema , Type [Schema ], Any ]:
52- return cls .Id
140+ return cls ._schema
53141
54142
55- class Ok (ControllerResponse ):
143+ class Ok (ControllerResponse [ T ] ):
56144 """
57145 Creates a 200 response with a detail information.
58146 {
@@ -61,53 +149,57 @@ class Ok(ControllerResponse):
61149
62150 Example:
63151 Ok('Saved Successfully') ==> 200, {detail: 'Saved Successfully'}
152+ OR
153+ class ASchema(BaseModel):
154+ name: str
155+ age: int
64156
157+ OK[ASchema](ASchema(name='Eadwin', age=18)) ==> 200, {detail: {'name':'Eadwin', 'age': 18}}
65158 """
66159
67160 status_code : int = status .HTTP_200_OK
68- detail : Union [ str , List [ Dict ], List [ str ], Dict ] = "Action was successful"
161+ _schema = OkSchema [ Any ] # type: ignore
69162
70163 def __init__ (self , message : Optional [Any ] = None ) -> None :
71164 super (Ok , self ).__init__ ()
72- self .detail = message or self .detail
73-
74- class Ok (Schema ):
75- detail : Union [str , List [Dict ], List [str ], Dict ]
165+ self .detail = message or "Action was successful"
76166
77167 def convert_to_schema (self ) -> Any :
78- return self .Ok .from_orm (self )
168+ return self ._schema .from_orm (self )
79169
80170 @classmethod
81171 def get_schema (cls ) -> Union [Schema , Type [Schema ], Any ]:
82- return cls .Ok
172+ return cls ._schema
83173
84174
85- class Detail (ControllerResponse ):
175+ class Detail (ControllerResponse [ T ] ):
86176 """
87177 Creates a custom response with detail information
88178 {
89179 detail: str| List[Dict] | List[str] | Dict,
90180 }
91181 Example:
92182 Detail('Invalid Request', 404) ==> 404, {detail: 'Invalid Request'}
183+ OR
184+ class ErrorSchema(BaseModel):
185+ message: str
186+
187+ Detail[ErrorSchema](dict(message='Bad Request'),400) ==> 400, {detail: {'message':'Bad Request'}}
93188 """
94189
95190 status_code : int = status .HTTP_200_OK
96- detail : Union [ str , List [ Dict ], List [ str ], Dict ] = dict ()
191+ _schema = DetailSchema [ Any ] # type: ignore
97192
98193 def __init__ (
99194 self , message : Optional [Any ] = None , status_code : int = status .HTTP_200_OK
100195 ) -> None :
101196 super (Detail , self ).__init__ ()
102- self .detail = message or self . detail
197+ self .detail = message or "Action was successful"
103198 self .status_code = status_code or self .status_code
104199
105- class Detail (Schema ):
106- detail : Union [str , List [Dict ], List [str ], Dict ]
107-
108200 def convert_to_schema (self ) -> Any :
109- return self .Detail .from_orm (self )
201+ return self ._schema .from_orm (self )
110202
111203 @classmethod
112204 def get_schema (cls ) -> Union [Schema , Type [Schema ], Any ]:
113- return cls .Detail
205+ return cls ._schema
0 commit comments