1
+ from math import ceil
1
2
from typing import Any
3
+ from typing import Optional
2
4
3
5
from sqlalchemy import delete
6
+ from sqlalchemy import func
7
+ from sqlalchemy import Select
4
8
from sqlalchemy import select
5
9
from sqlalchemy import update
6
10
from sqlalchemy .engine import Result
11
+ from sqlalchemy .orm import Session
12
+
13
+ from ..statement_maker import StatementMaker
7
14
8
15
9
16
class CreateMixin :
@@ -25,20 +32,24 @@ class Meta:
25
32
`output_schema_of_create` - marshmallow schema for serialization output data.
26
33
"""
27
34
28
- def create_object (self , ** data ) -> Result :
35
+ def create_object (self , ** kwargs ) -> Result :
29
36
"""If this method does not suit you, simply override it in your class."""
30
37
31
38
session = self ._get_option_from_meta ('session' )
32
39
model = self ._get_option_from_meta ('model' )
33
40
34
- new_obj = model (** data )
41
+ new_obj = model (** kwargs )
35
42
session .add (new_obj )
36
43
session .commit ()
37
44
return new_obj
38
45
39
- def create (self , data : dict , serialize : bool = False ) -> Result or dict :
40
- deserialized_data = self .deserialize_data ('input_schema_of_create' , data )
41
- new_object = self .create_object (** deserialized_data )
46
+ def create (
47
+ self , deserialize : bool = False , serialize : bool = False , ** kwargs : dict
48
+ ) -> Result or dict :
49
+ if deserialize :
50
+ kwargs = self .deserialize_data ('input_schema_of_create' , kwargs )
51
+
52
+ new_object = self .create_object (** kwargs )
42
53
43
54
if serialize :
44
55
return self .serialize_data ('output_schema_of_create' , new_object )
@@ -47,42 +58,163 @@ def create(self, data: dict, serialize: bool = False) -> Result or dict:
47
58
48
59
49
60
class ReadMixin :
50
- """Read object from database."""
61
+ """Read object from database.
51
62
52
- def get_object (self , id : Any ) -> Result :
53
- """If this method does not suit you, simply override it in your class."""
63
+ Mixin with get paginated result.
54
64
55
- session = self ._get_option_from_meta ('session' )
56
- model = self ._get_option_from_meta ('model' )
57
- return session .scalars (select (model ).where (model .id == id )).one ()
65
+ This mixin supports the following options in the Meta class:
66
+ ```
67
+ class CustomController(PaginationMixin, BaseCRUD):
68
+ class Meta:
69
+ session = Session
70
+ model = Model
71
+ filterable = ['id']
72
+ interval_filterable = ['id']
73
+ sortable = ['id']
74
+ searchable = ['id']
75
+ schema_of_paginate = Schema
58
76
59
- def read (self , id , serialize : bool = False ) -> Result or dict :
60
- obj = self .get_object (id )
77
+ custom_controller = CustomController()
78
+ ```
79
+ Required options:
80
+ `input_schema_of_read` - marshmallow schema for serialize.
81
+ `output_schema_of_read` - marshmallow schema for serialize.
82
+
83
+ Optional options:
84
+ `filterable` - list of fields allowed for filtration.
85
+ `interval_filterable` - list of fields allowed for filtration interval.
86
+ `sortable` - list of fields allowed for sorting.
87
+ `searchable` - a list of fields allowed for search for by substring.
88
+ """
89
+
90
+ def _calculate_items_per_page (
91
+ self , session : Session , statement : Select , per_page : int
92
+ ) -> tuple [int , int ]:
93
+ total = session .execute (
94
+ statement .with_only_columns (func .count ()).order_by (None )
95
+ ).scalar_one ()
96
+
97
+ if per_page == 0 :
98
+ pages = 0
99
+ else :
100
+ pages = ceil (total / per_page )
101
+
102
+ return pages , total
103
+
104
+ def _make_metadata (self , session : Session , page , per_page , statement ):
105
+ pages , total = self ._calculate_items_per_page (session , statement , per_page )
106
+ return {'pagination' : {'page' : page , 'per_page' : per_page , 'pages' : pages , 'total' : total }}
107
+
108
+ def paginate (
109
+ self ,
110
+ statement : Optional [Select ],
111
+ page : int = 1 ,
112
+ per_page : Optional [int ] = 20 ,
113
+ max_per_page : Optional [int ] = 100 ,
114
+ serialize : bool = False ,
115
+ include_metadata : bool = False ,
116
+ fields : Optional [list ] = None ,
117
+ ) -> dict :
118
+ session : Session = self ._get_option_from_meta ('session' )
119
+
120
+ if per_page > max_per_page :
121
+ per_page = max_per_page
122
+ elif per_page < 0 :
123
+ per_page = 0
124
+
125
+ if page < 1 :
126
+ page = 1
127
+
128
+ items = {'items' : []}
129
+ if include_metadata :
130
+ items ['_metadata' ] = self ._make_metadata (session , page , per_page , statement )
131
+
132
+ if per_page != 0 :
133
+ statement = statement .limit (per_page ).offset ((page - 1 ) * per_page )
134
+ paginated_rows = session .scalars (statement ).all ()
135
+ else :
136
+ paginated_rows = []
61
137
62
138
if serialize :
63
- return self .serialize_data ('output_schema_of_read' , obj )
139
+ items ['items' ] = self .serialize_data ('output_schema_of_read' , paginated_rows , fields )
140
+ else :
141
+ items ['items' ] = paginated_rows
142
+
143
+ return items
144
+
145
+ def read (
146
+ self ,
147
+ page : int = 1 ,
148
+ per_page : Optional [int ] = None ,
149
+ max_per_page : Optional [int ] = None ,
150
+ statement : Optional [Select ] = None ,
151
+ deserialize : bool = False ,
152
+ serialize : bool = False ,
153
+ include_metadata : bool = False ,
154
+ fields : Optional [list ] = None ,
155
+ ** kwargs ,
156
+ ) -> Result or dict :
157
+ model = self ._get_option_from_meta ('model' )
64
158
65
- return obj
159
+ if deserialize :
160
+ kwargs = self .deserialize_data ('input_schema_of_read' , kwargs )
161
+
162
+ filterable_fields = self ._get_option_from_meta ('filterable' , ())
163
+ interval_filterable_fields = self ._get_option_from_meta ('interval_filterable' , ())
164
+ searchable_fields = self ._get_option_from_meta ('searchable' , ())
165
+ sortable_fields = self ._get_option_from_meta ('sortable' , ())
166
+
167
+ stmt = StatementMaker (
168
+ model ,
169
+ kwargs ,
170
+ statement = statement ,
171
+ filterable_fields = filterable_fields ,
172
+ interval_filterable_fields = interval_filterable_fields ,
173
+ searchable_fields = searchable_fields ,
174
+ sortable_fields = sortable_fields ,
175
+ ).make_statement ()
176
+
177
+ if per_page is None :
178
+ per_page = self ._get_option_from_meta ('per_page' , 20 )
179
+
180
+ if max_per_page is None :
181
+ max_per_page = self ._get_option_from_meta ('max_per_page' , 100 )
182
+
183
+ items = self .paginate (
184
+ statement = stmt ,
185
+ page = page ,
186
+ per_page = per_page ,
187
+ max_per_page = max_per_page ,
188
+ serialize = serialize ,
189
+ include_metadata = include_metadata ,
190
+ fields = fields ,
191
+ )
192
+
193
+ return items
66
194
67
195
68
196
class UpdateMixin :
69
197
"""Update object in database."""
70
198
71
- def update_object (self , id : Any , ** data ) -> Result :
199
+ def update_object (self , id : Any , ** kwargs ) -> Result :
72
200
"""If this method does not suit you, simply override it in your class."""
73
201
74
202
session = self ._get_option_from_meta ('session' )
75
203
model = self ._get_option_from_meta ('model' )
76
204
77
- stmt = update (model ).where (model .id == id ).values (** data )
205
+ stmt = update (model ).where (model .id == id ).values (** kwargs )
78
206
session .execute (stmt )
79
207
80
208
obj = session .scalars (select (model ).where (model .id == id )).one ()
81
209
return obj
82
210
83
- def update (self , data : dict , serialize : bool = False ) -> Result or dict :
84
- deserialized_data = self .deserialize_data ('input_schema_of_update' , data )
85
- updated_object = self .update_object (** deserialized_data )
211
+ def update (
212
+ self , data : dict , deserialize : bool = False , serialize : bool = False
213
+ ) -> Result or dict :
214
+ if deserialize :
215
+ data = self .deserialize_data ('input_schema_of_update' , data )
216
+
217
+ updated_object = self .update_object (** data )
86
218
87
219
if serialize :
88
220
return self .serialize_data ('output_schema_of_update' , updated_object )
0 commit comments