lesscode-flask 0.2.63__tar.gz → 0.2.91__tar.gz

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 (75) hide show
  1. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/PKG-INFO +2 -2
  2. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/__init__.py +15 -1
  3. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/app.py +72 -3
  4. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/log/access_log_handler.py +8 -1
  5. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/model/access_log.py +3 -0
  6. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/setting/__init__.py +24 -0
  7. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/setup/__init__.py +434 -9
  8. lesscode_flask-0.2.91/lesscode_flask/utils/fs_util.py +68 -0
  9. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/limit/req_count/count_limiter_handler.py +16 -16
  10. lesscode_flask-0.2.91/lesscode_flask/utils/sign/__init__.py +3 -0
  11. lesscode_flask-0.2.91/lesscode_flask/utils/sign/signature.py +160 -0
  12. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/swagger/swagger_util.py +113 -5
  13. lesscode_flask-0.2.91/lesscode_flask/utils/task/__init__.py +0 -0
  14. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask.egg-info/PKG-INFO +2 -2
  15. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask.egg-info/SOURCES.txt +3 -0
  16. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask.egg-info/requires.txt +1 -1
  17. lesscode_flask-0.2.63/lesscode_flask/utils/fs_util.py +0 -55
  18. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/README.md +0 -0
  19. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/db/__init__.py +0 -0
  20. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/db/datasource.py +0 -0
  21. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/db/executor.py +0 -0
  22. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/export_data/__init__.py +0 -0
  23. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/export_data/data_download_handler.py +0 -0
  24. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/model/base_model.py +0 -0
  25. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/model/parameterized_query.py +0 -0
  26. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/model/resource_param_template.py +0 -0
  27. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/model/response_result.py +0 -0
  28. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/model/user.py +0 -0
  29. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/model/user_limit_policy.py +0 -0
  30. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/service/access_log_service.py +0 -0
  31. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/service/base_service.py +0 -0
  32. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/service/resource_param_template_service.py +0 -0
  33. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/signals.py +0 -0
  34. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/static/swagger.py +0 -0
  35. {lesscode_flask-0.2.63/lesscode_flask/utils/decorator → lesscode_flask-0.2.91/lesscode_flask/utils}/__init__.py +0 -0
  36. {lesscode_flask-0.2.63/lesscode_flask/utils/task → lesscode_flask-0.2.91/lesscode_flask/utils/decorator}/__init__.py +0 -0
  37. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/decorator/cache.py +0 -0
  38. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/decorator/sql_injection.py +0 -0
  39. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/decorator/swagger.py +0 -0
  40. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/dify_utils.py +0 -0
  41. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/file/file_exporter.py +0 -0
  42. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/file/file_utils.py +0 -0
  43. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/helpers.py +0 -0
  44. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/json/NotSortJSONProvider.py +0 -0
  45. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/limit/__init__.py +0 -0
  46. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/limit/consecutive/consecutive_limiter_handler.py +0 -0
  47. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/limit/consecutive/redis_consecutive_limiter.py +0 -0
  48. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/limit/limit_util.py +0 -0
  49. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/limit/req/rate_limiter_handler.py +0 -0
  50. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/limit/req/redis_rate_limiter.py +0 -0
  51. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/limit/req_count/redis_count_limiter.py +0 -0
  52. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/oss/__init__.py +0 -0
  53. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/oss/aliyun_oss.py +0 -0
  54. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/oss/ks3_oss.py +0 -0
  55. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/oss/minio_oss.py +0 -0
  56. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/redis/redis_helper.py +0 -0
  57. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/request/request.py +0 -0
  58. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/swagger/swagger_template.py +0 -0
  59. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/task/task_helper.py +0 -0
  60. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/utils/thread/thread_utils.py +0 -0
  61. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask/wsgi.py +0 -0
  62. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask.egg-info/dependency_links.txt +0 -0
  63. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/lesscode_flask.egg-info/top_level.txt +0 -0
  64. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/redash/query_runner/__init__.py +0 -0
  65. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/redash/query_runner/clickhouse.py +0 -0
  66. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/redash/query_runner/elasticsearch.py +0 -0
  67. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/redash/query_runner/kingbase.py +0 -0
  68. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/redash/query_runner/mysql.py +0 -0
  69. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/redash/query_runner/pg.py +0 -0
  70. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/redash/settings/__init__.py +0 -0
  71. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/redash/settings/helpers.py +0 -0
  72. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/redash/utils/__init__.py +0 -0
  73. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/redash/utils/requests_session.py +0 -0
  74. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/setup.cfg +0 -0
  75. {lesscode_flask-0.2.63 → lesscode_flask-0.2.91}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lesscode-flask
3
- Version: 0.2.63
3
+ Version: 0.2.91
4
4
  Summary: lesscode-flask 是基于flask的web开发脚手架项目,该项目初衷为简化开发过程,让研发人员更加关注业务。
5
5
  Home-page: https://lesscode-flask
6
6
  Author: Chao.yy
@@ -11,7 +11,7 @@ Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.9
12
12
  Description-Content-Type: text/markdown
13
13
  Requires-Dist: Flask==3.0.3
14
- Requires-Dist: lesscode-utils==0.0.91
14
+ Requires-Dist: lesscode-utils==0.0.94
15
15
  Requires-Dist: Flask-Login==0.6.3
16
16
  Requires-Dist: redis==5.1.1
17
17
  Requires-Dist: flask-swagger-ui==4.11.1
@@ -1,4 +1,4 @@
1
- __version__ = "0.2.63"
1
+ __version__ = "0.2.91"
2
2
 
3
3
  import functools
4
4
  import logging
@@ -16,6 +16,20 @@ class SQ_Blueprint(Blueprint):
16
16
  if not kwargs.get("import_name"):
17
17
  kwargs["import_name"] = __name__
18
18
  super().__init__(name=name, url_prefix=url_prefix, **kwargs)
19
+ # 记录蓝图内显式注册的“路径 + 方法”,供初始化阶段做冲突过滤。
20
+ self._route_method_keys = set()
21
+
22
+ def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options):
23
+ methods = options.get("methods")
24
+ if methods is None:
25
+ methods = ["GET"]
26
+ elif isinstance(methods, str):
27
+ methods = [methods]
28
+ methods = [str(item).upper() for item in methods if item]
29
+ for method in methods:
30
+ self._route_method_keys.add(f"{rule}|{method}")
31
+ return super().add_url_rule(rule, endpoint=endpoint, view_func=view_func,
32
+ provide_automatic_options=provide_automatic_options, **options)
19
33
 
20
34
  def decorator_handler(
21
35
  self,
@@ -18,7 +18,7 @@ from werkzeug.middleware.proxy_fix import ProxyFix
18
18
  from lesscode_flask.model.response_result import ResponseResult
19
19
  from lesscode_flask.setup import setup_blueprint, setup_logging, setup_query_runner, setup_swagger, setup_sql_alchemy, \
20
20
  setup_redis, setup_login_manager, setup_resource_register, setup_data_download, setup_scheduler, \
21
- setup_common_resource, setup_task
21
+ setup_common_resource, setup_task, setup_api_blueprint, prepare_api_blueprint_routes
22
22
  from lesscode_flask.signals import app_runed
23
23
  from lesscode_flask.utils.decorator.sql_injection import contains_sql_injection
24
24
  from lesscode_flask.utils.helpers import inject_args, generate_uuid, app_config
@@ -31,6 +31,7 @@ from lesscode_flask.utils.limit.req.rate_limiter_handler import RateLimitHandler
31
31
  from lesscode_flask.utils.limit.req_count.count_limiter_handler import CountLimitHandler
32
32
  from lesscode_flask.utils.limit.req_count.redis_count_limiter import RedisCountLimiter
33
33
  from lesscode_flask.utils.redis.redis_helper import RedisHelper
34
+ from lesscode_flask.utils.sign import verify_request_signature
34
35
  from lesscode_flask.utils.swagger.swagger_util import generate_openapi_spec
35
36
 
36
37
  # import collections.abc as cabc
@@ -74,6 +75,7 @@ class Lesscoder(Flask):
74
75
  self.consecutiveAccessLimiter = RedisConsecutiveAccessLimiter(self.config.get("REDIS_LIMIT_KEY", "redis"))
75
76
 
76
77
  def preprocess_request(self) -> ft.ResponseReturnValue | None:
78
+ verify_request_signature(self)
77
79
  v = super(Lesscoder, self).preprocess_request()
78
80
  user_limit_policy = None
79
81
  if hasattr(self, 'countLimiter') and self.countLimiter:
@@ -164,7 +166,9 @@ class Lesscoder(Flask):
164
166
  params_dict.update(view_args)
165
167
  SQL_INJECTION_ENABLE = self.config.get("SQL_INJECTION_ENABLE", False)
166
168
  if SQL_INJECTION_ENABLE:
167
- if contains_sql_injection(params_dict):
169
+ sql_injection_white_list = self.config.get("SQL_INJECTION_WHITE_LIST", []) or []
170
+ is_sql_injection_white_path = any(req.path.startswith(path) for path in sql_injection_white_list)
171
+ if not is_sql_injection_white_path and contains_sql_injection(params_dict):
168
172
  ResponseResult.fail("参数包含非法字符,请调整后重试!", status_code="403", http_code="403")
169
173
  # 调用处理函数执行请求处理
170
174
  result = self.ensure_sync(func)(**params_dict)
@@ -183,15 +187,80 @@ class Lesscoder(Flask):
183
187
  return result
184
188
 
185
189
  def setup(self):
190
+ def _collect_blueprint_route_method_keys(blueprint_map):
191
+ # 从项目内已定义的蓝图中提取“完整路径 + 方法”,用于后续排除同名自动接口。
192
+ route_method_keys = set()
193
+ for blueprint in blueprint_map.values():
194
+ prefix = (getattr(blueprint, "url_prefix", "") or "").rstrip("/")
195
+ # 优先使用 SQ_Blueprint 记录的路由元数据,避免依赖 Flask 闭包结构。
196
+ blueprint_keys = getattr(blueprint, "_route_method_keys", None)
197
+ if isinstance(blueprint_keys, set) and blueprint_keys:
198
+ for key in blueprint_keys:
199
+ if "|" not in key:
200
+ continue
201
+ route, method = key.rsplit("|", 1)
202
+ full_route = f"{prefix}{route}" if prefix else route
203
+ if not full_route.startswith("/"):
204
+ full_route = f"/{full_route}"
205
+ full_route = full_route.replace("//", "/")
206
+ route_method_keys.add(f"{full_route}|{str(method).upper()}")
207
+ continue
208
+
209
+ # 兜底方案:兼容普通 Blueprint 或未记录元数据的蓝图。
210
+ deferred_functions = getattr(blueprint, "deferred_functions", []) or []
211
+ for deferred in deferred_functions:
212
+ closure = getattr(deferred, "__closure__", None) or []
213
+ route = None
214
+ methods = None
215
+ for cell in closure:
216
+ value = cell.cell_contents
217
+ if isinstance(value, str) and value.startswith("/"):
218
+ route = value
219
+ elif isinstance(value, dict) and isinstance(value.get("methods"), (list, tuple, set)):
220
+ methods = [str(item).upper() for item in value.get("methods") if item]
221
+ if not route:
222
+ continue
223
+ full_route = f"{prefix}{route}" if prefix else route
224
+ if not full_route.startswith("/"):
225
+ full_route = f"/{full_route}"
226
+ full_route = full_route.replace("//", "/")
227
+ methods = methods or ["GET"]
228
+ for method in methods:
229
+ route_method_keys.add(f"{full_route}|{method}")
230
+ return route_method_keys
231
+
186
232
  setup_logging(self)
233
+ # 1) 先从能力平台准备自动蓝图路由计划(仅组织数据,不立即注册)。
234
+ api_capability_server, api_route_prefix, api_route_list = prepare_api_blueprint_routes(self)
235
+ # 1) 先收集项目代码中的蓝图定义。
187
236
  blueprint_map = setup_blueprint(self)
237
+ # 2) 用项目接口签名作为排除条件,确保同路径同方法时由项目接口覆盖自动接口。
238
+ project_route_method_keys = _collect_blueprint_route_method_keys(blueprint_map)
239
+ auto_blueprints = setup_api_blueprint(
240
+ self,
241
+ exclude_route_method_keys=project_route_method_keys,
242
+ existing_blueprint_names=set(blueprint_map.keys()),
243
+ route_list=api_route_list,
244
+ capability_server=api_capability_server,
245
+ route_prefix=api_route_prefix
246
+ )
188
247
  setup_query_runner()
189
248
  setup_swagger(self)
190
249
  setup_sql_alchemy(self)
191
250
  setup_redis(self)
192
251
  setup_login_manager(self)
252
+ # 3) 先注册自动蓝图,再注册项目蓝图;同时自动蓝图已做冲突过滤,保证项目实现优先。
253
+ if auto_blueprints:
254
+ for auto_blueprint in auto_blueprints:
255
+ self.register_blueprint(
256
+ auto_blueprint,
257
+ name=getattr(auto_blueprint, "_registration_name", auto_blueprint.name)
258
+ )
193
259
  for blueprint_name, blueprint in blueprint_map.items():
194
- self.register_blueprint(blueprint)
260
+ self.register_blueprint(
261
+ blueprint,
262
+ name=getattr(blueprint, "_registration_name", blueprint_name)
263
+ )
195
264
  setup_common_resource(self)
196
265
  setup_resource_register(self)
197
266
  setup_data_download(self)
@@ -45,6 +45,12 @@ class AccessLogHandler(Handler):
45
45
  app_key = request.headers.get("App-Key")
46
46
  url = request.path
47
47
  url_info_key = f"upms:url_info:{url}"
48
+ # 签名
49
+ signature = request.headers.get("signature") or request.headers.get('Signature')
50
+ # 随机数
51
+ nonce = request.headers.get("nonce") or request.headers.get('Nonce')
52
+ # 时间戳
53
+ timestamp = request.headers.get("timestamp") or request.headers.get('Timestamp')
48
54
 
49
55
  resource_id = "-"
50
56
  resource_label = url
@@ -66,7 +72,8 @@ class AccessLogHandler(Handler):
66
72
  obj_id=current_user.id, type=current_user.type, client_id=client_id,
67
73
  resource_id=resource_id, location=location, sub=current_user.sub,
68
74
  resource_label=resource_label, url=url, referrer=referrer, client_ip=client_ip,
69
- user_agent=user_agent_string, token=token, app_key=app_key, start_time=start_time,
75
+ user_agent=user_agent_string, token=token, app_key=app_key, signature=signature,
76
+ nonce=nonce, timestamp=timestamp, start_time=start_time,
70
77
  end_time=end_time, duration=end_time - start_time, status_code=status_code,
71
78
  params=params)
72
79
 
@@ -24,6 +24,9 @@ class AccessLog(BaseModel):
24
24
  user_agent = Column(String(512), comment='客户端')
25
25
  token = Column(String(64), comment='token')
26
26
  app_key = Column(String(64), comment='请求的app_key')
27
+ signature = Column(String(64), comment='签名')
28
+ nonce = Column(String(64), comment='签名随机数')
29
+ timestamp = Column(String(64), comment='签名时间戳')
27
30
  params = Column(JSONEncodedDict)
28
31
  start_time = Column(DOUBLE, comment='开始时间')
29
32
  end_time = Column(DOUBLE, comment='结束时间')
@@ -90,6 +90,22 @@ class BaseConfig:
90
90
  AUTH_DEFAULT_ACCESS = 0
91
91
  # 启用生成刷新token
92
92
  OAUTH2_REFRESH_TOKEN_GENERATOR = True
93
+ # 请求签名校验开关(在token校验前执行)
94
+ SIGN_ENABLE: bool = False
95
+ # 固定签名密钥,建议在业务配置中覆盖
96
+ SIGN_SECRET: str = ""
97
+ # 签名有效时间窗口(秒)
98
+ SIGN_WINDOW_SEC: int = 300
99
+ # 防重放校验开关
100
+ SIGN_NONCE_ENABLE: bool = True
101
+ # nonce缓存时长(秒)
102
+ SIGN_NONCE_TTL_SEC: int = 300
103
+ # 签名白名单路径前缀
104
+ SIGN_WHITE_LIST: list = [SWAGGER_URL, SWAGGER_API_URL, f"{ROUTE_PREFIX}/oauth/token", f"{ROUTE_PREFIX}/oauth/captcha"]
105
+ # 签名IP白名单(支持单IP或CIDR,命中后跳过签名校验)
106
+ SIGN_IP_WHITE_LIST: list = []
107
+ # 忽略签名校验的方法
108
+ SIGN_IGNORE_METHODS: list = ["OPTIONS"]
93
109
  # 是否启用限流频率验证
94
110
  RATE_LIMIT_ENABLE: bool = False
95
111
 
@@ -132,6 +148,8 @@ class BaseConfig:
132
148
  FS_OAM_SERVICE_URL = "https://oa.shangqi.com.cn"
133
149
  # sql 注入验证器开关
134
150
  SQL_INJECTION_ENABLE = False
151
+ # sql 注入检测白名单,命中路径前缀时跳过检测
152
+ SQL_INJECTION_WHITE_LIST: list = []
135
153
  #
136
154
  #
137
155
  # # 外网地址
@@ -141,6 +159,9 @@ class BaseConfig:
141
159
  #
142
160
  # 数据服务
143
161
  CAPABILITY_PLATFORM_SERVER: str = "http://127.0.0.1:8976"
162
+ # 转调能力平台时附加的自定义请求头(key/value 由业务配置覆盖)
163
+ DATA_SOURCE_KEY: str = "Data-Source-Id"
164
+ DATA_SOURCE_VALUE: str = ""
144
165
  # # 权限服务地址
145
166
  # OAUTH_SERVER: str = ""
146
167
  # # 后端管理地址
@@ -165,6 +186,9 @@ class BaseConfig:
165
186
  REGISTER_ENABLE = False
166
187
  REGISTER_SERVER = "http://127.0.0.1:8976"
167
188
 
189
+ # 通过固定 API 拉取地址并自动注册蓝图(默认关闭)
190
+ AUTO_BLUEPRINT_ENABLE = False
191
+
168
192
  # 本地环境
169
193
  ENV = "local"
170
194
  # 是否代理能力平台的公共接口