lesscode-flask 0.0.27__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. lesscode_flask/__init__.py +1 -0
  2. lesscode_flask/app.py +156 -0
  3. lesscode_flask/db/__init__.py +60 -0
  4. lesscode_flask/db/datasource.py +27 -0
  5. lesscode_flask/db/executor.py +128 -0
  6. lesscode_flask/log/access_log_handler.py +62 -0
  7. lesscode_flask/model/access_log.py +26 -0
  8. lesscode_flask/model/auth_client.py +42 -0
  9. lesscode_flask/model/auth_permission.py +25 -0
  10. lesscode_flask/model/base_model.py +38 -0
  11. lesscode_flask/model/parameterized_query.py +210 -0
  12. lesscode_flask/model/response_result.py +60 -0
  13. lesscode_flask/model/user.py +118 -0
  14. lesscode_flask/service/access_log_service.py +8 -0
  15. lesscode_flask/service/auth_client_service.py +7 -0
  16. lesscode_flask/service/auth_permission_service.py +7 -0
  17. lesscode_flask/service/authentication_service.py +67 -0
  18. lesscode_flask/service/base_service.py +138 -0
  19. lesscode_flask/setting/__init__.py +122 -0
  20. lesscode_flask/setup/__init__.py +185 -0
  21. lesscode_flask/utils/__init__.py +1 -0
  22. lesscode_flask/utils/decorator/__init__.py +0 -0
  23. lesscode_flask/utils/decorator/cache.py +126 -0
  24. lesscode_flask/utils/decorator/swagger.py +19 -0
  25. lesscode_flask/utils/file/file_exporter.py +98 -0
  26. lesscode_flask/utils/helpers.py +139 -0
  27. lesscode_flask/utils/json/NotSortJSONProvider.py +9 -0
  28. lesscode_flask/utils/oss/__init__.py +0 -0
  29. lesscode_flask/utils/oss/ks3_oss.py +203 -0
  30. lesscode_flask/utils/redis/redis_helper.py +117 -0
  31. lesscode_flask/utils/request/request.py +96 -0
  32. lesscode_flask/utils/swagger/swagger_template.py +82 -0
  33. lesscode_flask/utils/swagger/swagger_util.py +172 -0
  34. lesscode_flask/wsgi.py +37 -0
  35. lesscode_flask-0.0.27.dist-info/METADATA +127 -0
  36. lesscode_flask-0.0.27.dist-info/RECORD +46 -0
  37. lesscode_flask-0.0.27.dist-info/WHEEL +5 -0
  38. lesscode_flask-0.0.27.dist-info/top_level.txt +2 -0
  39. redash/query_runner/__init__.py +523 -0
  40. redash/query_runner/clickhouse.py +230 -0
  41. redash/query_runner/kingbase.py +228 -0
  42. redash/query_runner/mysql.py +309 -0
  43. redash/query_runner/pg.py +284 -0
  44. redash/settings/__init__.py +90 -0
  45. redash/settings/helpers.py +66 -0
  46. redash/utils/requests_session.py +18 -0
@@ -0,0 +1,210 @@
1
+ import re
2
+ from functools import partial
3
+ from numbers import Number
4
+
5
+ import pystache
6
+ from dateutil.parser import parse
7
+ from funcy import distinct
8
+
9
+ from lesscode_flask.utils.helpers import mustache_render
10
+
11
+
12
+ def _pluck_name_and_value(default_column, row):
13
+ row = {k.lower(): v for k, v in row.items()}
14
+ name_column = "name" if "name" in row.keys() else default_column.lower()
15
+ value_column = "value" if "value" in row.keys() else default_column.lower()
16
+
17
+ return {"name": row[name_column], "value": str(row[value_column])}
18
+
19
+
20
+ # def _load_result(query_id, org):
21
+ # from redash import models
22
+ #
23
+ # query = models.Query.get_by_id_and_org(query_id, org)
24
+ #
25
+ # if query.data_source:
26
+ # query_result = models.QueryResult.get_by_id_and_org(query.latest_query_data_id, org)
27
+ # return query_result.data
28
+ # else:
29
+ # raise QueryDetachedFromDataSourceError(query_id)
30
+
31
+
32
+ def dropdown_values(query_id, org):
33
+ data = [] # load_result(query_id, org)
34
+ first_column = data["columns"][0]["name"]
35
+ pluck = partial(_pluck_name_and_value, first_column)
36
+ return list(map(pluck, data["rows"]))
37
+
38
+
39
+ def join_parameter_list_values(parameters, schema):
40
+ updated_parameters = {}
41
+ for key, value in parameters.items():
42
+ if isinstance(value, list):
43
+ definition = next((definition for definition in schema if definition["name"] == key), {})
44
+ multi_values_options = definition.get("multiValuesOptions", {})
45
+ separator = str(multi_values_options.get("separator", ","))
46
+ prefix = str(multi_values_options.get("prefix", ""))
47
+ suffix = str(multi_values_options.get("suffix", ""))
48
+ updated_parameters[key] = separator.join([prefix + v + suffix for v in value])
49
+ else:
50
+ updated_parameters[key] = value
51
+ return updated_parameters
52
+
53
+
54
+ def _collect_key_names(nodes):
55
+ keys = []
56
+ for node in nodes._parse_tree:
57
+ if isinstance(node, pystache.parser._EscapeNode):
58
+ keys.append(node.key)
59
+ elif isinstance(node, pystache.parser._SectionNode):
60
+ keys.append(node.key)
61
+ keys.extend(_collect_key_names(node.parsed))
62
+
63
+ return distinct(keys)
64
+
65
+
66
+ def _collect_query_parameters(query):
67
+ nodes = pystache.parse(query)
68
+ keys = _collect_key_names(nodes)
69
+ return keys
70
+
71
+
72
+ def _parameter_names(parameter_values):
73
+ names = []
74
+ for key, value in parameter_values.items():
75
+ if isinstance(value, dict):
76
+ for inner_key in value.keys():
77
+ names.append("{}.{}".format(key, inner_key))
78
+ else:
79
+ names.append(key)
80
+
81
+ return names
82
+
83
+
84
+ def _is_number(string):
85
+ if isinstance(string, Number):
86
+ return True
87
+ else:
88
+ float(string)
89
+ return True
90
+
91
+
92
+ def _is_regex_pattern(value, regex):
93
+ try:
94
+ if re.compile(regex).fullmatch(value):
95
+ return True
96
+ else:
97
+ return False
98
+ except re.error:
99
+ return False
100
+
101
+
102
+ def _is_date(string):
103
+ parse(string)
104
+ return True
105
+
106
+
107
+ def _is_date_range(obj):
108
+ return _is_date(obj["start"]) and _is_date(obj["end"])
109
+
110
+
111
+ def _is_value_within_options(value, dropdown_options, allow_list=False):
112
+ if isinstance(value, list):
113
+ return allow_list and set(map(str, value)).issubset(set(dropdown_options))
114
+ return str(value) in dropdown_options
115
+
116
+
117
+ class ParameterizedQuery:
118
+ def __init__(self, template, schema=None, org=None):
119
+ self.schema = schema or []
120
+ self.template = template
121
+ self.query = template
122
+ self.parameters = {}
123
+
124
+ def apply(self, parameters):
125
+ invalid_parameter_names = [key for (key, value) in parameters.items() if not self._valid(key, value)]
126
+ if invalid_parameter_names:
127
+ raise InvalidParameterError(invalid_parameter_names)
128
+ else:
129
+ self.parameters.update(parameters)
130
+ self.query = mustache_render(self.template, **join_parameter_list_values(parameters, self.schema))
131
+
132
+ return self
133
+
134
+ def _valid(self, name, value):
135
+ if not self.schema:
136
+ return True
137
+
138
+ definition = next(
139
+ (definition for definition in self.schema if definition["name"] == name),
140
+ None,
141
+ )
142
+
143
+ if not definition:
144
+ return False
145
+
146
+ enum_options = definition.get("enumOptions")
147
+ query_id = definition.get("queryId")
148
+ regex = definition.get("regex")
149
+ allow_multiple_values = isinstance(definition.get("multiValuesOptions"), dict)
150
+
151
+ if isinstance(enum_options, str):
152
+ enum_options = enum_options.split("\n")
153
+
154
+ validators = {
155
+ "text": lambda value: isinstance(value, str),
156
+ "text-pattern": lambda value: _is_regex_pattern(value, regex),
157
+ "number": _is_number,
158
+ "enum": lambda value: _is_value_within_options(value, enum_options, allow_multiple_values),
159
+ "query": lambda value: _is_value_within_options(
160
+ value,
161
+ [v["value"] for v in dropdown_values(query_id, self.org)],
162
+ allow_multiple_values,
163
+ ),
164
+ "date": _is_date,
165
+ "datetime-local": _is_date,
166
+ "datetime-with-seconds": _is_date,
167
+ "date-range": _is_date_range,
168
+ "datetime-range": _is_date_range,
169
+ "datetime-range-with-seconds": _is_date_range,
170
+ }
171
+
172
+ validate = validators.get(definition["type"], lambda x: False)
173
+
174
+ try:
175
+ # multiple error types can be raised here; but we want to convert
176
+ # all except QueryDetached to InvalidParameterError in `apply`
177
+ return validate(value)
178
+ except QueryDetachedFromDataSourceError:
179
+ raise
180
+ except Exception:
181
+ return False
182
+
183
+ @property
184
+ def is_safe(self):
185
+ text_parameters = [param for param in self.schema if param["type"] == "text"]
186
+ return not any(text_parameters)
187
+
188
+ @property
189
+ def missing_params(self):
190
+ query_parameters = set(_collect_query_parameters(self.template))
191
+ return set(query_parameters) - set(_parameter_names(self.parameters))
192
+
193
+ @property
194
+ def text(self):
195
+ return self.query
196
+
197
+
198
+ class InvalidParameterError(Exception):
199
+ def __init__(self, parameters):
200
+ parameter_names = ", ".join(parameters)
201
+ message = "The following parameter values are incompatible with their definitions: {}".format(parameter_names)
202
+ super(InvalidParameterError, self).__init__(message)
203
+
204
+
205
+ class QueryDetachedFromDataSourceError(Exception):
206
+ def __init__(self, query_id):
207
+ self.query_id = query_id
208
+ super(QueryDetachedFromDataSourceError, self).__init__(
209
+ "This query is detached from any data source. Please select a different query."
210
+ )
@@ -0,0 +1,60 @@
1
+ # -*- coding: utf-8 -*-
2
+ import json
3
+ from datetime import datetime
4
+
5
+ from flask import Response, abort
6
+
7
+
8
+ class ResponseResult(dict):
9
+ """
10
+ ResponseResult 类用于统一包装数据返回格式
11
+ """
12
+
13
+ def __init__(self, status_code=("00000", "请求成功"), data=""):
14
+ """
15
+
16
+ :param status_code:
17
+ :param data:
18
+ """
19
+ super(ResponseResult, self).__init__()
20
+ # 业务请求状态编码
21
+ self["status"] = status_code[0]
22
+ # 返回状态码对应的说明信息
23
+ self["message"] = status_code[1]
24
+ # 返回数据对象 主对象 指定类型
25
+ self["data"] = data
26
+ # 时间戳
27
+ self["timestamp"] = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
28
+
29
+ @staticmethod
30
+ def make_response(message: str, data="", status_code="00000", http_code=200):
31
+ data = json.dumps(ResponseResult(status_code=(status_code, message), data=data))
32
+ respone = Response(data)
33
+ respone.content_type = "application/json"
34
+ respone.status_code = http_code
35
+ return respone
36
+
37
+ @staticmethod
38
+ def success(data,message: str=None):
39
+ """
40
+ 成功返回结果
41
+ :param data:
42
+ :param message:
43
+ :return:
44
+ """
45
+ if message:
46
+ return ResponseResult(data=data, status_code=("00000", message))
47
+ return ResponseResult(data=data)
48
+
49
+ @staticmethod
50
+ def fail(message: str, data="", status_code="99999", http_code=200):
51
+ """
52
+ 失败返回结果
53
+ :param message:
54
+ :param data:
55
+ :param status_code:
56
+ :param http_code:
57
+ :return:
58
+ """
59
+ respone = ResponseResult.make_response(message, data, status_code, http_code)
60
+ abort(respone)
@@ -0,0 +1,118 @@
1
+ from functools import reduce
2
+
3
+ from flask_login import AnonymousUserMixin, UserMixin
4
+
5
+
6
+ class PermissionsCheckMixin:
7
+ def has_permission(self, permission):
8
+ return self.has_permissions((permission,))
9
+
10
+ def has_permissions(self, permissions):
11
+ has_permissions = reduce(
12
+ lambda a, b: a and b,
13
+ [permission in self.permissions for permission in permissions],
14
+ True,
15
+ )
16
+
17
+ return has_permissions
18
+
19
+
20
+ class User(UserMixin, PermissionsCheckMixin):
21
+ """
22
+ 用户对象类
23
+ """
24
+
25
+ def __init__(self, id, username: str = None, display_name: str = None, phone_no: str = None, email: str = None,
26
+ org_id: str = None,
27
+ account_status: str = None, permissions=None):
28
+ # '账号id',
29
+ self.id = id
30
+ # 用户名
31
+ self.username = username
32
+ # '显示名',
33
+ self.display_name = display_name
34
+ # 手机号,
35
+ self.phone_no = phone_no
36
+ # 邮箱
37
+ self.email = email
38
+ # 组织机构id',
39
+ self.org_id = org_id
40
+ # '1正常(激活);2未激活(管理员新增,首次登录需要改密码); 3锁定(登录错误次数超限,锁定时长可配置); 4休眠(长期未登录(字段,时长可配置),定时) 5禁用-账号失效;
41
+ self.account_status = account_status
42
+ # 权限集合
43
+ self.permissions = permissions
44
+
45
+ @staticmethod
46
+ def is_api_user():
47
+ return False
48
+
49
+ @staticmethod
50
+ def is_anonymous_user():
51
+ return False
52
+
53
+ def __str__(self):
54
+ return (f"User(id={self.id},username={self.username},phone_no={self.phone_no},"
55
+ f"display_name={self.display_name},email={self.email},org_id={self.org_id},"
56
+ f"account_status={self.account_status},permissions={self.permissions})")
57
+
58
+ def __repr__(self):
59
+ return (f"User(id={self.id},username={self.username},phone_no={self.phone_no},"
60
+ f"display_name={self.display_name},email={self.email},org_id={self.org_id},"
61
+ f"account_status={self.account_status},permissions={self.permissions})")
62
+
63
+ def to_dict(self):
64
+ return {
65
+ "id": self.id,
66
+ "username": self.username,
67
+ "display_name": self.display_name,
68
+ "phone_no": self.phone_no,
69
+ "email": self.email if self.email else "",
70
+ "org_id": self.org_id if self.org_id else "",
71
+ "account_status": self.account_status,
72
+ "permissions": ",".join(self.permissions)
73
+ }
74
+
75
+
76
+ class AnonymousUser(User):
77
+ """
78
+ 匿名用户
79
+ """
80
+
81
+ def __init__(self, permissions=None):
82
+ super(AnonymousUser, self).__init__("AnonymousUserId", "AnonymousUser", "匿名用户", "-", "-", None, 1, permissions)
83
+
84
+ @staticmethod
85
+ def is_api_user():
86
+ return False
87
+
88
+ @staticmethod
89
+ def is_anonymous_user():
90
+ return True
91
+
92
+
93
+ class ApiUser(User):
94
+ def __init__(self, id, username: str = None, display_name: str = None,
95
+ permissions=None):
96
+ super(ApiUser, self).__init__(id, username, display_name, "-", "-", None, 1, permissions)
97
+
98
+ @staticmethod
99
+ def is_api_user():
100
+ return True
101
+
102
+ @staticmethod
103
+ def to_obj(_user):
104
+ user = ApiUser(_user.get("id"))
105
+ for key, value in _user.items():
106
+ if isinstance(key, bytes):
107
+ key = key.decode('utf-8')
108
+ if isinstance(value, bytes):
109
+ value = value.decode('utf-8')
110
+ if key == "permissions":
111
+ user.permissions = value.split(",")
112
+ elif key == "org_id":
113
+ user.org_id = value if value else None
114
+ elif key == "account_status":
115
+ user.account_status = int(value)
116
+ else:
117
+ setattr(user, key, value)
118
+ return user
@@ -0,0 +1,8 @@
1
+
2
+ from lesscode_flask.model.access_log import AccessLog
3
+ from lesscode_flask.service.base_service import BaseService
4
+
5
+
6
+ class AccessLogService(BaseService):
7
+ def __init__(self):
8
+ super().__init__(AccessLog)
@@ -0,0 +1,7 @@
1
+ from lesscode_flask.model.auth_client import AuthClient
2
+ from lesscode_flask.service.base_service import BaseService
3
+
4
+
5
+ class AuthClientService(BaseService):
6
+ def __init__(self):
7
+ super().__init__(AuthClient)
@@ -0,0 +1,7 @@
1
+ from lesscode_flask.model.auth_permission import AuthPermission
2
+ from lesscode_flask.service.base_service import BaseService
3
+
4
+
5
+ class AuthPermissionService(BaseService):
6
+ def __init__(self):
7
+ super().__init__(AuthPermission)
@@ -0,0 +1,67 @@
1
+ from lesscode_flask.model.auth_client import AuthClient
2
+ from lesscode_flask.model.auth_permission import AuthPermission
3
+ from lesscode_flask.model.user import ApiUser, User
4
+ from lesscode_flask.service.auth_client_service import AuthClientService
5
+ from lesscode_flask.service.auth_permission_service import AuthPermissionService
6
+ from lesscode_flask.utils.helpers import app_config
7
+ from lesscode_flask.utils.redis.redis_helper import RedisHelper
8
+
9
+
10
+ def get_token_user(token):
11
+ """
12
+ 使用API key 获取用户信息
13
+ :param apikey:
14
+ :return:
15
+ """
16
+ token_cache_key = f"oauth2:token:{token}"
17
+ # 优先从缓存中获取
18
+ access_token = RedisHelper(app_config.get("REDIS_OAUTH_KEY", "redis")).sync_hgetall(token_cache_key)
19
+ # {
20
+ # 'client_id': 'eYC0lOd1XVBBPpdDntMwFcPg',
21
+ # 'token_type': 'Bearer',
22
+ # 'access_token': 'Rsh1zS9QVzMAefB5G7be04CefK5opjiOCBtBS8BYYi',
23
+ # 'refresh_token': 'la5jb14acszPARoDf1KtH24JjnswSYUsjH4NbsQxOsvpO4Dl',
24
+ # 'scope': 'profile',
25
+ # 'issued_at': 1645614957,
26
+ # 'expires_in': 3600,
27
+ # 'user_id': 21,
28
+ # 'clientId': "",
29
+ # "grant_type": "",
30
+ # "is_only_one": "1",
31
+ # "only_type": "pc"
32
+ # }
33
+ if access_token:
34
+ user_id = access_token.get("user_id")
35
+ clientId = access_token.get("client_id")
36
+ user_cache_key = f"oauth2:client_user_info:client_id:{clientId}:user_id:{user_id}"
37
+ user_dict = RedisHelper(app_config.get("REDIS_OAUTH_KEY", "redis")).sync_hgetall(user_cache_key)
38
+ if user_dict:
39
+ user = User(user_id, user_dict["username"], user_dict["display_name"], user_dict["permissions"])
40
+ return user
41
+ return None
42
+
43
+
44
+ def get_api_user(apikey):
45
+ """
46
+ 使用API key 获取用户信息
47
+ :param apikey:
48
+ :return:
49
+ """
50
+ cache_key = f"oauth2:apikey_user_info:{apikey}"
51
+ # 优先从缓存中获取
52
+ user_dict = RedisHelper(app_config.get("REDIS_OAUTH_KEY", "redis")).sync_hgetall(cache_key)
53
+ if user_dict:
54
+ user = ApiUser.to_obj(user_dict)
55
+ return user
56
+ else:
57
+ # 库里查询
58
+ authClient = AuthClientService().get_one([AuthClient.client_id == apikey])
59
+ if authClient:
60
+ authPermission = AuthPermissionService().get_items([AuthPermission.client_id == authClient.id])
61
+ permissions = [permission.resource_id for permission in authPermission]
62
+ user = ApiUser(authClient.id, authClient.client_id, authClient.client_name, permissions)
63
+ RedisHelper(app_config.get("REDIS_OAUTH_KEY", "redis")).sync_hset(cache_key,
64
+ mapping=user.to_dict(),
65
+ time=authClient.token_expires_in)
66
+ return user
67
+ return None
@@ -0,0 +1,138 @@
1
+ import logging
2
+
3
+ from flask_login import current_user
4
+ from lesscode_flask.db import db
5
+ from lesscode_flask.model.base_model import BaseModel
6
+ from lesscode_flask.utils.helpers import serialize_result_to_dict, parameter_validation
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class BaseService:
12
+
13
+ def __init__(self, model):
14
+ self.model = model
15
+
16
+ @staticmethod
17
+ def add_item(item: BaseModel):
18
+ """
19
+ 添加数据
20
+ :param item: 待添加数据对象
21
+ :return:
22
+ """
23
+
24
+ # try:
25
+ #
26
+ # except AttributeError:
27
+ # current_user = None
28
+ try:
29
+ if hasattr(item, "create_user_id"):
30
+ item.create_user_id = current_user.id
31
+ if hasattr(item, "create_user_name"):
32
+ item.create_user_name = current_user.display_name
33
+ except Exception as e:
34
+ if hasattr(item, "create_user_id"):
35
+ item.create_user_id = "AnonymousUserId"
36
+ if hasattr(item, "create_user_name"):
37
+ item.create_user_name = "匿名用户"
38
+ db.session.add(item)
39
+ db.session.commit()
40
+ return item.id
41
+
42
+ def add_items(self, items: list):
43
+ """
44
+ 添加数据
45
+ :param items: 待添加数据对象集合
46
+ :return:
47
+ """
48
+ for item in items:
49
+ try:
50
+ if hasattr(item, "create_user_id"):
51
+ item.create_user_id = current_user.id
52
+ if hasattr(item, "create_user_name"):
53
+ item.create_user_name = current_user.display_name
54
+ except Exception as e:
55
+ if hasattr(item, "create_user_id"):
56
+ item.create_user_id = "AnonymousUserId"
57
+ if hasattr(item, "create_user_name"):
58
+ item.create_user_name = "匿名用户"
59
+ db.session.execute(
60
+ self.model.__table__.insert(),
61
+ items
62
+ )
63
+ db.session.commit()
64
+ return items
65
+
66
+ def update_item(self, id: str, item: dict):
67
+
68
+ try:
69
+ if hasattr(self.model, "modify_user_id"):
70
+ item["modify_user_id"] = current_user.id
71
+ if hasattr(self.model, "modify_user_name"):
72
+ item["modify_user_name"] = current_user.display_name
73
+ except Exception as e:
74
+ if hasattr(item, "modify_user_id"):
75
+ item.create_user_id = "AnonymousUserId"
76
+ if hasattr(item, "modify_user_name"):
77
+ item.create_user_name = "匿名用户"
78
+ self.model.query.filter_by(id=id).update(parameter_validation(item))
79
+ db.session.commit()
80
+ return id
81
+
82
+ def get_item(self, id: str):
83
+ """
84
+ 获取单条信息
85
+ :param id:
86
+ :return:
87
+ """
88
+ item = self.model.query.get(id)
89
+ return item
90
+
91
+ def get_one(self, filters: list):
92
+ """
93
+ 获取单条信息
94
+ :param filters:
95
+ :return:
96
+ """
97
+ query = self.model.query
98
+ if filters:
99
+ query = query.filter(*filters)
100
+ item = query.one()
101
+ return item
102
+
103
+ def get_items(self, filters: list = None):
104
+ """
105
+ 获取列表信息
106
+ :param filters:
107
+ :return:
108
+ """
109
+ query = self.model.query
110
+ if filters:
111
+ query = query.filter(*filters)
112
+ items = query.all()
113
+ return items
114
+
115
+ def delete_item(self, id: str):
116
+ self.model.query.filter_by(id=id).delete()
117
+ return id
118
+
119
+ def delete_items(self, filters: list):
120
+ if filters and len(filters) > 0:
121
+ self.model.query.filter(*filters).delete()
122
+ return id
123
+
124
+ def page(self, columns: [], filters: list = None, page_num: int = 1, page_size: int = 10):
125
+ query = self.model.query
126
+ if filters:
127
+ query = query.filter(*filters)
128
+ pagination = query.paginate(page=page_num, per_page=page_size)
129
+ # 获取当前页的数据
130
+ items = pagination.items
131
+ # 获取分页信息
132
+ total = pagination.total
133
+ has_prev = pagination.has_prev
134
+ has_next = pagination.has_next
135
+ result = {"columns": columns, "dataSource": serialize_result_to_dict(items), "total": total,
136
+ "has_prev": has_prev,
137
+ "has_next": has_next}
138
+ return result