precis-cli 0.1.3__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 (442) hide show
  1. app/__init__.py +16 -0
  2. app/api/__init__.py +32 -0
  3. app/api/dependencies.py +185 -0
  4. app/api/main.py +301 -0
  5. app/api/middleware/__init__.py +34 -0
  6. app/api/middleware/exception_handler.py +126 -0
  7. app/api/middleware/request_logging.py +97 -0
  8. app/api/middleware/token_auth.py +215 -0
  9. app/api/models/__init__.py +151 -0
  10. app/api/models/connection_rules.py +98 -0
  11. app/api/models/files.py +99 -0
  12. app/api/models/full_validation.py +554 -0
  13. app/api/models/project.py +117 -0
  14. app/api/models/projects.py +80 -0
  15. app/api/models/schema.py +121 -0
  16. app/api/models/v2_responses.py +46 -0
  17. app/api/models/validation.py +303 -0
  18. app/api/models/workspace.py +118 -0
  19. app/api/routers/__init__.py +56 -0
  20. app/api/routers/ai/__init__.py +36 -0
  21. app/api/routers/ai/chat.py +243 -0
  22. app/api/routers/ai/generate.py +156 -0
  23. app/api/routers/ai/hardware.py +199 -0
  24. app/api/routers/ai/jobs.py +544 -0
  25. app/api/routers/ai/migrate.py +471 -0
  26. app/api/routers/ai/models.py +352 -0
  27. app/api/routers/ai/ollama.py +108 -0
  28. app/api/routers/ai/providers.py +491 -0
  29. app/api/routers/ai/router.py +44 -0
  30. app/api/routers/ai/stream.py +281 -0
  31. app/api/routers/ai/utils.py +182 -0
  32. app/api/routers/core/__init__.py +34 -0
  33. app/api/routers/core/connection_rules.py +188 -0
  34. app/api/routers/core/data_sources.py +374 -0
  35. app/api/routers/core/regex.py +333 -0
  36. app/api/routers/core/reporting.py +263 -0
  37. app/api/routers/files/__init__.py +24 -0
  38. app/api/routers/files/ops.py +179 -0
  39. app/api/routers/files/transfer.py +101 -0
  40. app/api/routers/preview/__init__.py +35 -0
  41. app/api/routers/preview/content_mode.py +264 -0
  42. app/api/routers/preview/header_row.py +154 -0
  43. app/api/routers/preview/models.py +81 -0
  44. app/api/routers/preview/path_mode.py +440 -0
  45. app/api/routers/preview/router.py +30 -0
  46. app/api/routers/project/__init__.py +61 -0
  47. app/api/routers/project/base.py +111 -0
  48. app/api/routers/project/constraint.py +345 -0
  49. app/api/routers/project/full_config.py +411 -0
  50. app/api/routers/project/full_config_writer.py +327 -0
  51. app/api/routers/project/helpers.py +167 -0
  52. app/api/routers/project/inspection_fix.py +330 -0
  53. app/api/routers/project/manifest.py +605 -0
  54. app/api/routers/project/models.py +177 -0
  55. app/api/routers/project/pattern.py +268 -0
  56. app/api/routers/project/regex.py +358 -0
  57. app/api/routers/project/scanner.py +157 -0
  58. app/api/routers/project/schema.py +567 -0
  59. app/api/routers/project/schema_helpers.py +149 -0
  60. app/api/routers/project/settings.py +399 -0
  61. app/api/routers/project/template.py +325 -0
  62. app/api/routers/project/validation.py +227 -0
  63. app/api/routers/project/view.py +177 -0
  64. app/api/routers/project/workspaces.py +167 -0
  65. app/api/routers/projects/__init__.py +25 -0
  66. app/api/routers/projects/create.py +121 -0
  67. app/api/routers/projects/open.py +103 -0
  68. app/api/routers/projects/scan.py +100 -0
  69. app/api/routers/validation/__init__.py +39 -0
  70. app/api/routers/validation/common.py +263 -0
  71. app/api/routers/validation/content_mode.py +401 -0
  72. app/api/routers/validation/history.py +235 -0
  73. app/api/routers/validation/inline_mode.py +196 -0
  74. app/api/routers/validation/path_mode.py +314 -0
  75. app/api/routers/validation/router.py +51 -0
  76. app/api/services/full_validation_response_builder.py +412 -0
  77. app/api/services/io_error_messages.py +41 -0
  78. app/api/services/preview_service.py +248 -0
  79. app/cli/__init__.py +18 -0
  80. app/cli/__main__.py +30 -0
  81. app/cli/shared_services/__init__.py +42 -0
  82. app/cli/shared_services/config_ops.py +581 -0
  83. app/cli/shared_services/generation_ops.py +160 -0
  84. app/cli/shared_services/project_ops.py +288 -0
  85. app/cli/shell/__init__.py +48 -0
  86. app/cli/shell/commands/__init__.py +63 -0
  87. app/cli/shell/commands/ai/__init__.py +260 -0
  88. app/cli/shell/commands/ai/chat.py +331 -0
  89. app/cli/shell/commands/ai/delete.py +165 -0
  90. app/cli/shell/commands/ai/diff.py +70 -0
  91. app/cli/shell/commands/ai/display.py +428 -0
  92. app/cli/shell/commands/ai/executor.py +225 -0
  93. app/cli/shell/commands/ai/executor_utils.py +271 -0
  94. app/cli/shell/commands/ai/generate.py +285 -0
  95. app/cli/shell/commands/ai/interaction.py +262 -0
  96. app/cli/shell/commands/ai/migrate.py +281 -0
  97. app/cli/shell/commands/ai/resolver.py +224 -0
  98. app/cli/shell/commands/ai/status.py +101 -0
  99. app/cli/shell/commands/ai/switch.py +158 -0
  100. app/cli/shell/commands/ai/utils.py +52 -0
  101. app/cli/shell/commands/base.py +343 -0
  102. app/cli/shell/commands/config/__init__.py +131 -0
  103. app/cli/shell/commands/config/base.py +80 -0
  104. app/cli/shell/commands/config/check.py +246 -0
  105. app/cli/shell/commands/config/edit.py +148 -0
  106. app/cli/shell/commands/config/get.py +111 -0
  107. app/cli/shell/commands/config/init.py +123 -0
  108. app/cli/shell/commands/config/inspect.py +164 -0
  109. app/cli/shell/commands/config/list.py +104 -0
  110. app/cli/shell/commands/config/set.py +119 -0
  111. app/cli/shell/commands/config/show.py +150 -0
  112. app/cli/shell/commands/exit.py +67 -0
  113. app/cli/shell/commands/help.py +104 -0
  114. app/cli/shell/commands/infer_schema.py +156 -0
  115. app/cli/shell/commands/open.py +201 -0
  116. app/cli/shell/commands/project.py +215 -0
  117. app/cli/shell/commands/provider.py +619 -0
  118. app/cli/shell/commands/system.py +170 -0
  119. app/cli/shell/commands/validate.py +455 -0
  120. app/cli/shell/completer.py +231 -0
  121. app/cli/shell/config_storage.py +191 -0
  122. app/cli/shell/exceptions.py +90 -0
  123. app/cli/shell/formatter.py +371 -0
  124. app/cli/shell/interactive_menu.py +348 -0
  125. app/cli/shell/main.py +286 -0
  126. app/cli/shell/parser.py +266 -0
  127. app/cli/start.py +180 -0
  128. app/cli_main.py +52 -0
  129. app/mcp_server.py +311 -0
  130. app/shared/core/__init__.py +18 -0
  131. app/shared/core/app_version.py +59 -0
  132. app/shared/core/config/__init__.py +119 -0
  133. app/shared/core/config/server.py +193 -0
  134. app/shared/core/data_source/__init__.py +106 -0
  135. app/shared/core/data_source/loader.py +348 -0
  136. app/shared/core/data_source/loaders/__init__.py +213 -0
  137. app/shared/core/data_source/loaders/base.py +215 -0
  138. app/shared/core/data_source/loaders/converter.py +337 -0
  139. app/shared/core/data_source/loaders/csv_loader.py +252 -0
  140. app/shared/core/data_source/loaders/excel_loader.py +666 -0
  141. app/shared/core/data_source/loaders/extractor.py +268 -0
  142. app/shared/core/data_source/loaders/json_loader.py +354 -0
  143. app/shared/core/data_source/loaders/registry.py +88 -0
  144. app/shared/core/data_source/loaders/sql_loader.py +313 -0
  145. app/shared/core/data_source/loaders/strategies/__init__.py +100 -0
  146. app/shared/core/data_source/loaders/strategies/array_parser.py +277 -0
  147. app/shared/core/data_source/loaders/strategies/lines_parser.py +355 -0
  148. app/shared/core/data_source/loaders/strategies/object_parser.py +278 -0
  149. app/shared/core/data_source/schema_info.py +50 -0
  150. app/shared/core/data_source/specs/__init__.py +128 -0
  151. app/shared/core/data_source/specs/base.py +258 -0
  152. app/shared/core/data_source/specs/csv_source.py +137 -0
  153. app/shared/core/data_source/specs/excel_source.py +141 -0
  154. app/shared/core/data_source/specs/file_base.py +252 -0
  155. app/shared/core/data_source/specs/json_source.py +226 -0
  156. app/shared/core/data_source/specs/sql_source.py +161 -0
  157. app/shared/core/encoding.py +36 -0
  158. app/shared/core/io/__init__.py +24 -0
  159. app/shared/core/io/yaml.py +379 -0
  160. app/shared/core/manifest_schema/__init__.py +52 -0
  161. app/shared/core/manifest_schema/types.py +99 -0
  162. app/shared/core/manifest_schema/version.py +175 -0
  163. app/shared/core/patterns/__init__.py +24 -0
  164. app/shared/core/patterns/loader.py +134 -0
  165. app/shared/core/patterns/writer.py +244 -0
  166. app/shared/core/project/__init__.py +47 -0
  167. app/shared/core/project/constraint/builders/__init__.py +50 -0
  168. app/shared/core/project/constraint/builders/base.py +100 -0
  169. app/shared/core/project/constraint/builders/composite.py +77 -0
  170. app/shared/core/project/constraint/builders/conditional.py +67 -0
  171. app/shared/core/project/constraint/builders/foreign_key.py +53 -0
  172. app/shared/core/project/constraint/builders/registry.py +64 -0
  173. app/shared/core/project/constraint/builders/scripted.py +51 -0
  174. app/shared/core/project/constraint/builders/single_column.py +86 -0
  175. app/shared/core/project/constraint/builders/unique.py +53 -0
  176. app/shared/core/project/constraint/factory.py +170 -0
  177. app/shared/core/project/constraint/reader.py +214 -0
  178. app/shared/core/project/constraint/registry.py +233 -0
  179. app/shared/core/project/constraint/types/__init__.py +63 -0
  180. app/shared/core/project/constraint/types/constraint_file.py +261 -0
  181. app/shared/core/project/constraint/types/refs.py +460 -0
  182. app/shared/core/project/constraint/types.py +28 -0
  183. app/shared/core/project/constraint/writer.py +181 -0
  184. app/shared/core/project/loader/__init__.py +56 -0
  185. app/shared/core/project/loader/loader.py +30 -0
  186. app/shared/core/project/loader/loader_parts/config_inspector.py +137 -0
  187. app/shared/core/project/loader/loader_parts/embedded_constraints.py +224 -0
  188. app/shared/core/project/loader/loader_parts/file_loaders.py +58 -0
  189. app/shared/core/project/loader/loader_parts/inspection_ids.py +85 -0
  190. app/shared/core/project/loader/loader_parts/inspector_helpers.py +312 -0
  191. app/shared/core/project/loader/loader_parts/inspector_id_checks.py +274 -0
  192. app/shared/core/project/loader/loader_parts/inspector_reference_checks.py +690 -0
  193. app/shared/core/project/loader/loader_parts/inspector_uniqueness_checks.py +286 -0
  194. app/shared/core/project/loader/loader_parts/loading_error_messages.py +298 -0
  195. app/shared/core/project/loader/loader_parts/main.py +432 -0
  196. app/shared/core/project/loader/loader_parts/path_validation.py +117 -0
  197. app/shared/core/project/loader/loader_parts/runtime.py +125 -0
  198. app/shared/core/project/loader/types.py +246 -0
  199. app/shared/core/project/manifest/coverage.py +391 -0
  200. app/shared/core/project/manifest/reader.py +260 -0
  201. app/shared/core/project/manifest/types.py +91 -0
  202. app/shared/core/project/manifest/types_parts/__init__.py +60 -0
  203. app/shared/core/project/manifest/types_parts/constants.py +40 -0
  204. app/shared/core/project/manifest/types_parts/data_source.py +68 -0
  205. app/shared/core/project/manifest/types_parts/info.py +59 -0
  206. app/shared/core/project/manifest/types_parts/manifest.py +294 -0
  207. app/shared/core/project/manifest/types_parts/refs.py +153 -0
  208. app/shared/core/project/manifest/types_parts/settings.py +92 -0
  209. app/shared/core/project/manifest/types_parts/settings_file_processing.py +64 -0
  210. app/shared/core/project/manifest/types_parts/settings_script_security.py +78 -0
  211. app/shared/core/project/manifest/types_parts/settings_validation.py +83 -0
  212. app/shared/core/project/manifest/types_parts/template.py +66 -0
  213. app/shared/core/project/manifest/writer.py +262 -0
  214. app/shared/core/project/manual_data/__init__.py +27 -0
  215. app/shared/core/project/manual_data/types.py +75 -0
  216. app/shared/core/project/regex/reader.py +197 -0
  217. app/shared/core/project/regex/types.py +405 -0
  218. app/shared/core/project/regex/writer.py +123 -0
  219. app/shared/core/project/schema/reader.py +170 -0
  220. app/shared/core/project/schema/types.py +47 -0
  221. app/shared/core/project/schema/types_parts/__init__.py +36 -0
  222. app/shared/core/project/schema/types_parts/column.py +174 -0
  223. app/shared/core/project/schema/types_parts/column_utils.py +72 -0
  224. app/shared/core/project/schema/types_parts/constraint.py +165 -0
  225. app/shared/core/project/schema/types_parts/schema_id.py +66 -0
  226. app/shared/core/project/schema/types_parts/source.py +255 -0
  227. app/shared/core/project/schema/types_parts/source_options.py +347 -0
  228. app/shared/core/project/schema/types_parts/table.py +230 -0
  229. app/shared/core/project/schema/writer.py +139 -0
  230. app/shared/core/project/schema_ref_check.py +95 -0
  231. app/shared/core/project/template/__init__.py +27 -0
  232. app/shared/core/project/template/expander.py +263 -0
  233. app/shared/core/project/template/reader.py +120 -0
  234. app/shared/core/project/template/types.py +114 -0
  235. app/shared/core/project/transform/reader.py +76 -0
  236. app/shared/core/project/transform/types.py +116 -0
  237. app/shared/core/project/transform/writer.py +84 -0
  238. app/shared/core/pydantic_messages.py +59 -0
  239. app/shared/core/reporter/__init__.py +46 -0
  240. app/shared/core/reporter/reporter.py +220 -0
  241. app/shared/core/reporter/reporters/__init__.py +65 -0
  242. app/shared/core/reporter/reporters/base.py +188 -0
  243. app/shared/core/reporter/reporters/dingtalk_app_reporter.py +274 -0
  244. app/shared/core/reporter/reporters/email_reporter.py +271 -0
  245. app/shared/core/reporter/reporters/feishu_app_reporter.py +467 -0
  246. app/shared/core/reporter/reporters/local_file_reporter.py +208 -0
  247. app/shared/core/reporter/reporters/wecom_app_reporter.py +268 -0
  248. app/shared/core/utils/__init__.py +18 -0
  249. app/shared/core/utils/path_utils.py +60 -0
  250. app/shared/core/utils/regex_extract.py +113 -0
  251. app/shared/domain/__init__.py +114 -0
  252. app/shared/domain/constraints/__init__.py +76 -0
  253. app/shared/domain/constraints/allowed_values.py +211 -0
  254. app/shared/domain/constraints/base.py +141 -0
  255. app/shared/domain/constraints/charset.py +337 -0
  256. app/shared/domain/constraints/composite.py +174 -0
  257. app/shared/domain/constraints/condition_registry.py +130 -0
  258. app/shared/domain/constraints/conditional.py +629 -0
  259. app/shared/domain/constraints/date_logic.py +731 -0
  260. app/shared/domain/constraints/foreign_key.py +261 -0
  261. app/shared/domain/constraints/key_normalization.py +56 -0
  262. app/shared/domain/constraints/not_null.py +185 -0
  263. app/shared/domain/constraints/range.py +360 -0
  264. app/shared/domain/constraints/regex.py +218 -0
  265. app/shared/domain/constraints/scripted.py +276 -0
  266. app/shared/domain/constraints/unique.py +233 -0
  267. app/shared/domain/data_engine.py +384 -0
  268. app/shared/domain/data_types.py +113 -0
  269. app/shared/domain/data_types_parts/__init__.py +67 -0
  270. app/shared/domain/data_types_parts/base.py +204 -0
  271. app/shared/domain/data_types_parts/composite.py +250 -0
  272. app/shared/domain/data_types_parts/expression.py +202 -0
  273. app/shared/domain/data_types_parts/extracted.py +106 -0
  274. app/shared/domain/data_types_parts/json_types.py +234 -0
  275. app/shared/domain/data_types_parts/scalars.py +719 -0
  276. app/shared/domain/data_types_parts/sequence.py +129 -0
  277. app/shared/domain/dataset_schema.py +48 -0
  278. app/shared/domain/eval_sandbox.py +63 -0
  279. app/shared/domain/expression_system.py +366 -0
  280. app/shared/domain/regex_flags.py +53 -0
  281. app/shared/domain/schema/builder.py +223 -0
  282. app/shared/domain/schema/models.py +339 -0
  283. app/shared/domain/transforms/__init__.py +28 -0
  284. app/shared/domain/transforms/aggregate.py +141 -0
  285. app/shared/domain/transforms/base.py +171 -0
  286. app/shared/domain/transforms/cast_type.py +120 -0
  287. app/shared/domain/transforms/concat.py +103 -0
  288. app/shared/domain/transforms/conditional_assign.py +114 -0
  289. app/shared/domain/transforms/date_format.py +81 -0
  290. app/shared/domain/transforms/digits.py +77 -0
  291. app/shared/domain/transforms/drop_duplicates.py +94 -0
  292. app/shared/domain/transforms/fill_na.py +94 -0
  293. app/shared/domain/transforms/filter_rows.py +97 -0
  294. app/shared/domain/transforms/lookup.py +78 -0
  295. app/shared/domain/transforms/lower_case.py +70 -0
  296. app/shared/domain/transforms/map_value.py +90 -0
  297. app/shared/domain/transforms/math_expr.py +112 -0
  298. app/shared/domain/transforms/modulo.py +82 -0
  299. app/shared/domain/transforms/regex_extract.py +103 -0
  300. app/shared/domain/transforms/registry.py +95 -0
  301. app/shared/domain/transforms/replace.py +88 -0
  302. app/shared/domain/transforms/sort_rows.py +94 -0
  303. app/shared/domain/transforms/string_split.py +84 -0
  304. app/shared/domain/transforms/strip.py +73 -0
  305. app/shared/domain/transforms/substring.py +92 -0
  306. app/shared/domain/transforms/upper_case.py +70 -0
  307. app/shared/domain/transforms/weighted_sum.py +104 -0
  308. app/shared/domain/validation_constraints.py +69 -0
  309. app/shared/services/__init__.py +62 -0
  310. app/shared/services/ai/__init__.py +48 -0
  311. app/shared/services/ai/agent/__init__.py +40 -0
  312. app/shared/services/ai/agent/chat_tools/__init__.py +47 -0
  313. app/shared/services/ai/agent/chat_tools/apply_actions.py +592 -0
  314. app/shared/services/ai/agent/chat_tools/ask_user.py +237 -0
  315. app/shared/services/ai/agent/chat_tools/read_canvas.py +140 -0
  316. app/shared/services/ai/agent/chat_tools/read_project.py +160 -0
  317. app/shared/services/ai/agent/chat_tools/read_table.py +301 -0
  318. app/shared/services/ai/agent/chat_tools/schemas.py +105 -0
  319. app/shared/services/ai/agent/chat_tools/validate_table.py +131 -0
  320. app/shared/services/ai/agent/executor.py +554 -0
  321. app/shared/services/ai/agent/memory.py +256 -0
  322. app/shared/services/ai/agent/planner.py +282 -0
  323. app/shared/services/ai/agent/tool_registry.py +296 -0
  324. app/shared/services/ai/agent/tools/__init__.py +32 -0
  325. app/shared/services/ai/agent/tools/config_generate.py +133 -0
  326. app/shared/services/ai/agent/tools/config_refine.py +109 -0
  327. app/shared/services/ai/agent/tools/config_validate.py +403 -0
  328. app/shared/services/ai/agent/tools/merge_results.py +136 -0
  329. app/shared/services/ai/agent/tools/plan_chunks.py +79 -0
  330. app/shared/services/ai/agent/tools/script_parse.py +359 -0
  331. app/shared/services/ai/agent/types.py +138 -0
  332. app/shared/services/ai/chat_agent_runner.py +596 -0
  333. app/shared/services/ai/chat_orchestrator.py +725 -0
  334. app/shared/services/ai/failure_messages.py +65 -0
  335. app/shared/services/ai/job_storage.py +274 -0
  336. app/shared/services/ai/migrate_service.py +550 -0
  337. app/shared/services/ai/streaming/__init__.py +65 -0
  338. app/shared/services/ai/streaming/event_journal.py +197 -0
  339. app/shared/services/ai/streaming/orchestrator.py +262 -0
  340. app/shared/services/ai/streaming/pending_interaction_store.py +227 -0
  341. app/shared/services/ai/streaming/sse_response.py +148 -0
  342. app/shared/services/ai/streaming/types.py +73 -0
  343. app/shared/services/ai/types.py +213 -0
  344. app/shared/services/ai/utils.py +322 -0
  345. app/shared/services/diff/config_diff.py +320 -0
  346. app/shared/services/hardware.py +237 -0
  347. app/shared/services/llm/__init__.py +62 -0
  348. app/shared/services/llm/actions/__init__.py +42 -0
  349. app/shared/services/llm/actions/_canvas_validator.py +147 -0
  350. app/shared/services/llm/actions/_constraint_validator.py +401 -0
  351. app/shared/services/llm/actions/_regex_validator.py +85 -0
  352. app/shared/services/llm/actions/_schema_validator.py +82 -0
  353. app/shared/services/llm/actions/_settings_validator.py +76 -0
  354. app/shared/services/llm/actions/_transform_validator.py +78 -0
  355. app/shared/services/llm/actions/action_handlers.py +400 -0
  356. app/shared/services/llm/actions/action_parser.py +63 -0
  357. app/shared/services/llm/actions/action_processor.py +468 -0
  358. app/shared/services/llm/actions/action_validator.py +311 -0
  359. app/shared/services/llm/actions/diff_compute.py +195 -0
  360. app/shared/services/llm/actions/regex_handlers.py +284 -0
  361. app/shared/services/llm/actions/registry.py +336 -0
  362. app/shared/services/llm/actions/schema_handlers.py +335 -0
  363. app/shared/services/llm/actions/settings_handlers.py +180 -0
  364. app/shared/services/llm/actions/specs.py +259 -0
  365. app/shared/services/llm/actions/transform_handlers.py +279 -0
  366. app/shared/services/llm/actions/validation_types.py +147 -0
  367. app/shared/services/llm/cache/__init__.py +18 -0
  368. app/shared/services/llm/cache/response_cache.py +80 -0
  369. app/shared/services/llm/chat/__init__.py +35 -0
  370. app/shared/services/llm/chat/chat_system_prompt.py +561 -0
  371. app/shared/services/llm/chat/response_parser.py +296 -0
  372. app/shared/services/llm/config/__init__.py +45 -0
  373. app/shared/services/llm/config/crypto.py +135 -0
  374. app/shared/services/llm/config/loader.py +258 -0
  375. app/shared/services/llm/config/models.py +202 -0
  376. app/shared/services/llm/config/presets.py +128 -0
  377. app/shared/services/llm/config_generator.py +163 -0
  378. app/shared/services/llm/constraints/__init__.py +41 -0
  379. app/shared/services/llm/constraints/constraint_builder.py +177 -0
  380. app/shared/services/llm/constraints/constraint_deletion.py +84 -0
  381. app/shared/services/llm/constraints/constraint_id.py +174 -0
  382. app/shared/services/llm/constraints/frontend_instructions.py +333 -0
  383. app/shared/services/llm/constraints/inline_batch.py +279 -0
  384. app/shared/services/llm/discovery/__init__.py +35 -0
  385. app/shared/services/llm/discovery/scanner.py +181 -0
  386. app/shared/services/llm/generation/__init__.py +42 -0
  387. app/shared/services/llm/generation/agent_wiring.py +156 -0
  388. app/shared/services/llm/generation/config_builder.py +386 -0
  389. app/shared/services/llm/generation/errors.py +36 -0
  390. app/shared/services/llm/generation/existing_config.py +99 -0
  391. app/shared/services/llm/generation/profiler.py +279 -0
  392. app/shared/services/llm/generation/prompt_builder.py +199 -0
  393. app/shared/services/llm/generation/response_parser.py +144 -0
  394. app/shared/services/llm/generation/service.py +622 -0
  395. app/shared/services/llm/models.py +104 -0
  396. app/shared/services/llm/providers/__init__.py +48 -0
  397. app/shared/services/llm/providers/base.py +310 -0
  398. app/shared/services/llm/providers/cached_provider.py +90 -0
  399. app/shared/services/llm/providers/ollama.py +498 -0
  400. app/shared/services/llm/providers/openai.py +334 -0
  401. app/shared/services/llm/providers/registry.py +108 -0
  402. app/shared/services/llm/schema_resolver.py +141 -0
  403. app/shared/services/llm/suggestion_utils.py +219 -0
  404. app/shared/services/llm/validate_executor.py +163 -0
  405. app/shared/services/llm/yaml_io.py +406 -0
  406. app/shared/services/preview/__init__.py +18 -0
  407. app/shared/services/preview/loader.py +145 -0
  408. app/shared/services/preview/path_validation.py +138 -0
  409. app/shared/services/project_loader.py +57 -0
  410. app/shared/services/schema_inference.py +260 -0
  411. app/shared/services/schema_runtime_builder.py +120 -0
  412. app/shared/services/validation/__init__.py +52 -0
  413. app/shared/services/validation/chunked_loader.py +509 -0
  414. app/shared/services/validation/dag/__init__.py +35 -0
  415. app/shared/services/validation/dag/builder.py +141 -0
  416. app/shared/services/validation/dag/executor.py +225 -0
  417. app/shared/services/validation/dag/sorter.py +77 -0
  418. app/shared/services/validation/data_loader.py +231 -0
  419. app/shared/services/validation/engine.py +446 -0
  420. app/shared/services/validation/executor.py +1030 -0
  421. app/shared/services/validation/extractors.py +266 -0
  422. app/shared/services/validation/history.py +209 -0
  423. app/shared/services/validation/json_payload.py +136 -0
  424. app/shared/services/validation/loader.py +152 -0
  425. app/shared/services/validation/memory_monitor.py +179 -0
  426. app/shared/services/validation/postprocess.py +208 -0
  427. app/shared/services/validation/progress.py +64 -0
  428. app/shared/services/validation/report_export.py +222 -0
  429. app/shared/services/validation/resolver.py +180 -0
  430. app/shared/services/validation/service.py +418 -0
  431. app/shared/services/validation/types.py +238 -0
  432. app/shared/services/validation/validators/__init__.py +36 -0
  433. app/shared/services/validation/validators/adapter.py +182 -0
  434. app/shared/services/validation/validators/base.py +332 -0
  435. app/shared/services/validation/validators/composite.py +233 -0
  436. app/shared/services/validation/validators/date_logic.py +276 -0
  437. app/start_server.py +133 -0
  438. precis_cli-0.1.3.dist-info/METADATA +180 -0
  439. precis_cli-0.1.3.dist-info/RECORD +442 -0
  440. precis_cli-0.1.3.dist-info/WHEEL +5 -0
  441. precis_cli-0.1.3.dist-info/entry_points.txt +4 -0
  442. precis_cli-0.1.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,204 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # Copyright 2026 Precis Team
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """
17
+ @fileoverview 数据类型基类模块
18
+
19
+ 功能概述:
20
+ - 定义数据类型抽象基类 (DataType)
21
+ - 所有具体数据类型都继承自此基类
22
+ - 提供统一的验证 (validate) 和解析 (parse) 接口
23
+
24
+ 架构设计:
25
+ - 抽象基类: 使用 ABC 定义抽象方法
26
+ - 双重接口: validate() + parse()
27
+ - 子类必须实现两个抽象方法才能实例化
28
+
29
+ 输入示例:
30
+ # 子类使用
31
+ class IntegerType(DataType):
32
+ def validate(self, value): ...
33
+ def parse(self, value): ...
34
+
35
+ 输出示例:
36
+ # validate(value: Any) -> Tuple[bool, Any]
37
+ # - 验证值是否符合类型要求
38
+ # - 返回: (是否有效, 错误信息)
39
+ # - 成功: (True, None)
40
+ # - 失败: (False, "错误描述")
41
+
42
+ # parse(value: Any) -> Any
43
+ # - 将值转换为目标类型
44
+ # - 返回转换后的值
45
+
46
+ 继承体系:
47
+ DataType (抽象基类)
48
+ ├── IntegerType # 整数类型
49
+ ├── StringType # 字符串类型
50
+ ├── FloatType # 浮点数类型
51
+ ├── BooleanType # 布尔类型
52
+ ├── ExpressionType # 表达式类型
53
+ ├── ExtractedType # 提取类型
54
+ ├── SequenceType # 序列类型
55
+ └── CompositeConditionType # 复合条件类型
56
+ """
57
+
58
+ from __future__ import annotations
59
+
60
+ # 1. 标准库导入
61
+ from abc import ABC, abstractmethod
62
+ from typing import Any
63
+
64
+ # 2. 第三方库导入
65
+ import pandas as pd
66
+
67
+
68
+ class DataType(ABC):
69
+ """
70
+ @classdesc 数据类型抽象基类
71
+
72
+ 所有具体数据类型的基类,定义统一的数据类型接口。
73
+ 子类必须实现 validate() 和 parse() 方法,否则无法实例化。
74
+
75
+ 使用场景:
76
+ - 作为 IntegerType、StringType、FloatType 等具体类型的基类
77
+ - 在 ColumnSchema 中定义列的数据类型
78
+ - 在数据引擎 process_dataframe 中执行类型验证和转换
79
+ """
80
+
81
+ @abstractmethod
82
+ def validate(self, value: Any) -> tuple[bool, Any]:
83
+ """
84
+ @methoddesc 验证值是否符合类型要求
85
+
86
+ 子类必须实现此方法,对单个原始值进行类型合规性检查。
87
+ 验证逻辑应专注于格式和语义正确性,不涉及空值判断(空值由调用方处理)。
88
+
89
+ Args:
90
+ value: 要验证的原始值,可能为字符串、数字、日期对象等任意类型
91
+
92
+ Returns:
93
+ tuple[bool, Any]: 二元组,结构固定为 (is_valid, error_message)
94
+ - is_valid (bool): True 表示值符合该数据类型的格式和语义要求;False 表示验证失败
95
+ - error_message (Any): 验证失败时返回描述性错误信息(通常为 str);验证通过时必须返回 None
96
+
97
+ Raises:
98
+ NotImplementedError: 抽象方法,直接调用基类方法会抛出此异常,强制子类实现
99
+
100
+ 示例:
101
+ >>> dtype = IntegerType()
102
+ >>> dtype.validate(123)
103
+ (True, None)
104
+ >>> dtype.validate("abc")
105
+ (False, "'abc' 不是一个严格格式的整数(只允许数字和可选的负号)。")
106
+ """
107
+ raise NotImplementedError
108
+
109
+ @abstractmethod
110
+ def parse(self, value: Any) -> Any:
111
+ """
112
+ @methoddesc 将值转换为目标类型
113
+
114
+ 子类必须实现此方法,将已通过 validate() 验证的原始值转换为该数据类型的标准 Python 对象。
115
+ 调用方应确保在调用 parse() 前先调用 validate() 确认值有效。
116
+
117
+ Args:
118
+ value: 要转换的原始值,通常为字符串或从数据源读取的原始格式
119
+
120
+ Returns:
121
+ Any: 转换后的目标类型值,如 int、float、datetime 对象等,具体类型由子类定义
122
+
123
+ Raises:
124
+ NotImplementedError: 抽象方法,直接调用基类方法会抛出此异常,强制子类实现
125
+
126
+ 示例:
127
+ >>> dtype = IntegerType()
128
+ >>> dtype.parse("123")
129
+ 123
130
+ """
131
+ raise NotImplementedError
132
+
133
+ def process_column(self, series: pd.Series, col_name: str, nullable: bool = True) -> tuple[pd.Series, list[dict]]:
134
+ """
135
+ @methoddesc 对整列数据进行验证和解析(向量化入口)
136
+
137
+ 提供逐元素验证 + 解析的默认实现,作为子类覆写前的基线行为。
138
+ 处理流程按优先级分为四个分支:非空违反 -> 空值透传 -> 类型验证失败 -> 解析成功。
139
+ 子类可覆写此方法以提供更高效的向量化实现(如使用 pandas 的 apply/vectorized 操作)。
140
+
141
+ Args:
142
+ series: 待处理的 pandas Series,包含从数据源读取的原始列数据
143
+ col_name: 列名,用于构造错误信息中的标识和描述
144
+ nullable: 是否允许空值(None/NaN/空字符串)。False 表示该列不允许为空,遇到空值时记录 NotNullViolation 错误
145
+
146
+ Returns:
147
+ tuple[pd.Series, list[dict]]: 处理结果二元组
148
+ - parsed_series (pd.Series): 解析后的 Series,索引与输入一致;验证失败或空值位置填充为 None
149
+ - errors_list (list[dict]): 错误字典列表,每个字典包含 row_index、column、value、error_type、error_message,格式与 data_engine 保持一致
150
+ """
151
+ # 收集整列处理过程中发现的所有错误
152
+ errors: list[dict] = []
153
+ # 收集逐元素解析后的值,最终组装为新的 Series
154
+ parsed_values: list[Any] = []
155
+ # 预先计算整列的空值掩码,避免在循环中重复判断,提升性能
156
+ is_na = series.isna()
157
+
158
+ for index, value in series.items():
159
+ # 分支 1:非空约束检查
160
+ # 当 nullable=False 时,拒绝任何空值(pandas NaN 或空字符串)
161
+ if not nullable and (is_na[index] or str(value).strip() == ""):
162
+ errors.append(
163
+ {
164
+ "row_index": index, # 数据行索引,与原始 DataFrame 对齐
165
+ "column": col_name, # 当前处理的列名,用于定位错误位置
166
+ "value": value, # 原始空值,保留用于调试和展示
167
+ "error_type": "NotNullViolation", # 错误类型标识,与前端错误分类对应
168
+ "error_message": f"列 '{col_name}' 不允许为空", # 面向用户的错误描述
169
+ }
170
+ )
171
+ # 非空违反时,解析结果置为 None,保持 Series 长度与索引一致
172
+ parsed_values.append(None)
173
+ # 进入下一个元素,跳过后续的 validate/parse
174
+ continue
175
+
176
+ # 分支 2:空值透传
177
+ # 当列允许为空(nullable=True)且当前值为空时,直接透传 None,不执行类型验证
178
+ if is_na[index]:
179
+ parsed_values.append(None)
180
+ continue
181
+
182
+ # 分支 3:类型验证
183
+ # 对非空值调用子类实现的 validate() 进行格式和语义检查
184
+ is_valid, error_message = self.validate(value)
185
+ if not is_valid:
186
+ errors.append(
187
+ {
188
+ "row_index": index,
189
+ "column": col_name,
190
+ "value": value, # 记录原始无效值,便于用户排查数据问题
191
+ "error_type": "TypeValidationError", # 类型验证失败的统一错误标识
192
+ "error_message": error_message, # 由具体子类提供的详细错误描述
193
+ }
194
+ )
195
+ # 验证失败时,解析结果置为 None,避免将无效数据流入下游处理
196
+ parsed_values.append(None)
197
+ continue
198
+
199
+ # 分支 4:解析成功
200
+ # 值已通过 validate() 验证,调用子类实现的 parse() 转换为目标类型
201
+ parsed_values.append(self.parse(value))
202
+
203
+ # 使用原始索引构建新的 Series,确保与输入 DataFrame 的行对齐
204
+ return pd.Series(parsed_values, index=series.index), errors
@@ -0,0 +1,250 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # Copyright 2026 Precis Team
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """
17
+ @fileoverview 复合数据类型模块
18
+
19
+ 功能概述:
20
+ - 定义复合数据类型 (如复合条件)
21
+ - 支持逻辑组合 (AND/OR) 的条件表达式
22
+
23
+ 架构设计:
24
+ - 继承基类: 继承自 DataType 基类
25
+ - 条件解析: 使用正则表达式分割复合条件
26
+ - 注册表匹配: 使用 registry 匹配子条件
27
+
28
+ 输入示例:
29
+ # 复合条件表达式
30
+ value = "age > 18 AND status = 'active'"
31
+
32
+ # logical_op = "and" 时,使用 " AND " 分割
33
+ # logical_op = "or" 时,使用 " OR " 分割
34
+
35
+ 输出示例:
36
+ # validate() 返回
37
+ (True, None) # 所有子条件都有效
38
+ (False, "复合条件中的子句 'xxx' 不匹配任何已知的模式。")
39
+
40
+ # parse() 返回解析后的条件列表
41
+ [
42
+ {"field": "age", "operator": ">", "value": 18},
43
+ {"field": "status", "operator": "=", "value": "active"}
44
+ ]
45
+ """
46
+
47
+ from __future__ import annotations
48
+
49
+ # 1. 标准库导入
50
+ import re
51
+ from typing import Any
52
+
53
+ # 2. 项目内部导入
54
+ from app.shared.domain.data_types_parts.base import DataType
55
+ from app.shared.domain.expression_system import ExpressionRegistry
56
+
57
+
58
+ class CompositeConditionType(DataType):
59
+ """
60
+ @classdesc 复合条件类型
61
+
62
+ 支持逻辑组合(AND/OR)的条件表达式类型。
63
+ 将复合条件字符串按逻辑运算符分割为原子条件,逐个验证。
64
+
65
+ 使用场景:
66
+ - 验证包含多个子条件的复合条件表达式
67
+ - 如 "age > 18 AND status = 'active'"
68
+ """
69
+
70
+ def __init__(self, registry: ExpressionRegistry, logical_op: str = "and") -> None:
71
+ """
72
+ @methoddesc 初始化复合条件类型
73
+
74
+ 参数:
75
+ registry: 表达式注册表,用于匹配原子条件
76
+ logical_op: 逻辑运算符,"and" 或 "or"
77
+ """
78
+ self.registry = registry
79
+ self.splitter = re.compile(r"\s+" + re.escape(logical_op) + r"\s+")
80
+
81
+ def validate(self, value: Any) -> tuple[bool, Any]:
82
+ """
83
+ @methoddesc 验证复合条件表达式
84
+
85
+ 将表达式按逻辑运算符分割为原子条件,逐个使用注册表匹配验证。
86
+
87
+ 参数:
88
+ value: 条件表达式字符串
89
+
90
+ 返回:
91
+ 元组 (is_valid, error_message)
92
+ """
93
+ if not isinstance(value, str):
94
+ return False, f"期望是字符串, 但得到了 {type(value).__name__}。"
95
+ if not value.strip():
96
+ return True, None
97
+ atomic_conditions = self.splitter.split(value)
98
+ for i, atomic_str in enumerate(atomic_conditions):
99
+ atomic_str = atomic_str.strip()
100
+ if not atomic_str:
101
+ return False, f"在第 {i} 个 '{self.splitter.pattern}' 分割符附近发现了空的条件表达式。"
102
+ match_result = self.registry.find_match(atomic_str)
103
+ if not match_result:
104
+ return False, f"复合条件中的子句 '{atomic_str}' 不匹配任何已知的模式。"
105
+ pattern, match = match_result
106
+ try:
107
+ pattern.parser_func(match.groupdict())
108
+ except (ValueError, KeyError) as e:
109
+ return False, f"子句 '{atomic_str}' 验证失败: {e}"
110
+ return True, None
111
+
112
+ def parse(self, value: Any) -> list[dict[str, Any]]:
113
+ """
114
+ @methoddesc 解析复合条件表达式
115
+
116
+ 将表达式按逻辑运算符分割为原子条件,逐个解析为结构化数据。
117
+
118
+ 参数:
119
+ value: 条件表达式字符串
120
+
121
+ 返回:
122
+ 解析后的条件列表,每个元素是包含 type 和 value 的字典
123
+ """
124
+ if not isinstance(value, str) or not value.strip():
125
+ return []
126
+ parsed_conditions = []
127
+ atomic_conditions = self.splitter.split(value)
128
+ for atomic_str in atomic_conditions:
129
+ atomic_str = atomic_str.strip()
130
+ if not atomic_str:
131
+ continue
132
+ find_result = self.registry.find_match(atomic_str)
133
+ if find_result is None:
134
+ raise ValueError(f"子条件 '{atomic_str}' 不匹配任何已注册的表达式模式")
135
+ pattern, match = find_result
136
+ parsed_value = pattern.parser_func(match.groupdict())
137
+ parsed_conditions.append({"type": pattern.name, "value": parsed_value})
138
+ return parsed_conditions
139
+
140
+
141
+ class SpecificCompositeConditionType(DataType):
142
+ """
143
+ @classdesc 特定复合条件类型
144
+
145
+ 针对单一模式的精确复合条件类型。
146
+ 初始化时指定具体模式名称,验证时仅匹配该模式。
147
+
148
+ 使用场景:
149
+ - 需要限制复合条件中只能使用特定模式时
150
+ """
151
+
152
+ def __init__(self, registry: ExpressionRegistry, pattern: str, logical_op: str = "and") -> None:
153
+ """
154
+ @methoddesc 初始化特定复合条件类型
155
+
156
+ 参数:
157
+ registry: 表达式注册表
158
+ pattern: 指定的模式名称
159
+ logical_op: 逻辑运算符,"and" 或 "or"
160
+
161
+ 抛出:
162
+ ValueError: 当注册表中未找到指定模式时抛出
163
+ """
164
+ self.registry = registry
165
+ self.pattern_name = pattern
166
+ self.specific_pattern = None
167
+ for p in registry._patterns:
168
+ if p.name == pattern:
169
+ self.specific_pattern = p
170
+ break
171
+ if self.specific_pattern is None:
172
+ raise ValueError(f"在注册表中未找到指定的模式: '{pattern}'")
173
+ self.splitter = re.compile(r"\s+" + re.escape(logical_op) + r"\s+")
174
+
175
+ def validate(self, value: Any) -> tuple[bool, Any]:
176
+ """
177
+ @methoddesc 验证特定复合条件表达式
178
+
179
+ 与通用 CompositeConditionType 不同,本方法不仅要求子句能被注册表匹配,
180
+ 还要求匹配到的模式与初始化时指定的 specific_pattern 一致。
181
+ 过去本方法忽略了 specific_pattern,导致限定模式后仍接受其他子句。
182
+
183
+ 参数:
184
+ value: 条件表达式字符串
185
+
186
+ 返回:
187
+ 元组 (is_valid, error_message)
188
+ """
189
+ specific = self.specific_pattern
190
+ if specific is None:
191
+ return False, "内部错误:specific_pattern 未初始化"
192
+ if not isinstance(value, str):
193
+ return False, f"期望是字符串, 但得到了 {type(value).__name__}。"
194
+ if not value.strip():
195
+ return True, None
196
+ atomic_conditions = self.splitter.split(value)
197
+ for i, atomic_str in enumerate(atomic_conditions):
198
+ atomic_str = atomic_str.strip()
199
+ if not atomic_str:
200
+ return False, f"在第 {i} 个 '{self.splitter.pattern}' 分割符附近发现了空的条件表达式。"
201
+ match_result = self.registry.find_match(atomic_str)
202
+ if not match_result:
203
+ return False, f"复合条件中的子句 '{atomic_str}' 不匹配任何已知的模式。"
204
+ pattern, match = match_result
205
+ # 校验匹配到的模式必须与 specific_pattern 一致
206
+ if pattern is None or pattern.name != specific.name:
207
+ return (
208
+ False,
209
+ f"子句 '{atomic_str}' 匹配的模式 '{getattr(pattern, 'name', '?')}' 与限定模式 "
210
+ f"'{specific.name}' 不一致,本类型只允许使用限定模式。",
211
+ )
212
+ try:
213
+ pattern.parser_func(match.groupdict())
214
+ except (ValueError, KeyError) as e:
215
+ return False, f"子句 '{atomic_str}' 验证失败: {e}"
216
+ return True, None
217
+
218
+ def parse(self, value: Any) -> list[dict[str, Any]]:
219
+ """
220
+ @methoddesc 解析特定复合条件表达式
221
+
222
+ 仅解析与 specific_pattern 匹配的子句,忽略其他模式(与 validate 语义一致)。
223
+
224
+ 参数:
225
+ value: 条件表达式字符串
226
+
227
+ 返回:
228
+ 解析后的条件列表,每个元素是包含 type 和 value 的字典
229
+ """
230
+ if not isinstance(value, str) or not value.strip():
231
+ return []
232
+ specific = self.specific_pattern
233
+ if specific is None:
234
+ return []
235
+ parsed_conditions = []
236
+ atomic_conditions = self.splitter.split(value)
237
+ for atomic_str in atomic_conditions:
238
+ atomic_str = atomic_str.strip()
239
+ if not atomic_str:
240
+ continue
241
+ match_result = self.registry.find_match(atomic_str)
242
+ if not match_result:
243
+ continue
244
+ pattern, match = match_result
245
+ # 仅解析与 specific_pattern 一致的子句
246
+ if pattern is None or pattern.name != specific.name:
247
+ continue
248
+ parsed_value = pattern.parser_func(match.groupdict())
249
+ parsed_conditions.append({"type": pattern.name, "value": parsed_value})
250
+ return parsed_conditions
@@ -0,0 +1,202 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # Copyright 2026 Precis Team
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ r"""
17
+ @fileoverview 表达式数据类型模块
18
+
19
+ 功能概述:
20
+ - 定义 ExpressionType 支持从注册表匹配和验证表达式格式
21
+ - 定义 SpecificExpressionType 针对单一模式的精确匹配
22
+ - 提供 validate() 和 parse() 方法用于表达式校验与解析
23
+
24
+ 架构设计:
25
+ - 继承基类: 继承自 DataType 基类,遵循统一的数据类型接口
26
+ - 注册表驱动: 使用 ExpressionRegistry 匹配表达式模式
27
+ - 解析委托: 将具体解析逻辑委托给 pattern.parser_func
28
+
29
+ 输入示例:
30
+ # 注册表中的表达式模式
31
+ registry = ExpressionRegistry(patterns=[
32
+ Pattern(name="add", pattern=r"add\({a},{b}\)", parser_func=...)
33
+ ])
34
+
35
+ # 验证表达式
36
+ ExpressionType(registry).validate("add(1,2)") # (True, None)
37
+ ExpressionType(registry).validate("unknown(1,2)") # (False, "不匹配任何已注册的表达式格式")
38
+
39
+ 输出示例:
40
+ # validate() 返回
41
+ (True, None) # 表达式有效
42
+ (False, "表达式 'xxx' 格式正确但内容无效: ...") # 表达式格式错
43
+
44
+ # parse() 返回
45
+ {
46
+ "type": "add",
47
+ "value": {"a": 1, "b": 2}
48
+ }
49
+ """
50
+
51
+ from __future__ import annotations
52
+
53
+ # 1. 标准库导入
54
+ from typing import Any
55
+
56
+ # 2. 项目内部导入
57
+ from app.shared.domain.data_types_parts.base import DataType
58
+ from app.shared.domain.expression_system import ExpressionPattern, ExpressionRegistry
59
+
60
+
61
+ class ExpressionType(DataType):
62
+ """
63
+ @classdesc 表达式类型
64
+
65
+ 支持从注册表匹配和验证表达式格式的数据类型。
66
+ 使用 ExpressionRegistry 查找匹配的表达式模式,常用于验证手机号、邮箱等格式。
67
+
68
+ 使用场景:
69
+ - 验证数据是否符合预定义的表达式模式
70
+ - 将符合模式的字符串解析为结构化数据
71
+ """
72
+
73
+ name = "Expr"
74
+
75
+ def __init__(self, registry: ExpressionRegistry) -> None:
76
+ """
77
+ @methoddesc 初始化表达式类型
78
+
79
+ 参数:
80
+ registry: 表达式注册表,包含所有可用的表达式模式
81
+ """
82
+ self.registry = registry
83
+
84
+ def validate(self, value: Any) -> tuple[bool, Any]:
85
+ """
86
+ @methoddesc 验证表达式字符串
87
+
88
+ 在注册表中查找匹配的模式,并调用模式的解析函数验证内容。
89
+
90
+ 参数:
91
+ value: 要验证的字符串
92
+
93
+ 返回:
94
+ 元组 (is_valid, error_message)
95
+ """
96
+ if not isinstance(value, str):
97
+ return False, f"期望是字符串, 但得到了 {type(value).__name__}。"
98
+ match_result = self.registry.find_match(value)
99
+ if not match_result:
100
+ return False, f"'{value}' 不匹配任何已注册的表达式格式。"
101
+ pattern, match = match_result
102
+ try:
103
+ pattern.parser_func(match.groupdict())
104
+ return True, None
105
+ except (ValueError, KeyError) as e:
106
+ return False, f"表达式 '{value}' 格式正确但内容无效: {e}"
107
+
108
+ def parse(self, value: Any) -> dict[str, Any]:
109
+ """
110
+ @methoddesc 解析表达式字符串
111
+
112
+ 在注册表中查找匹配的模式,并调用解析函数生成结构化数据。
113
+
114
+ 参数:
115
+ value: 要解析的字符串
116
+
117
+ 返回:
118
+ 包含 type 和 value 的字典
119
+ """
120
+ find_result = self.registry.find_match(value)
121
+ if find_result is None:
122
+ raise ValueError(f"值 '{value}' 不匹配任何已注册的表达式模式")
123
+ pattern, match = find_result
124
+ return {"type": pattern.name, "value": pattern.parser_func(match.groupdict())}
125
+
126
+
127
+ class SpecificExpressionType(DataType):
128
+ """
129
+ @classdesc 特定表达式类型
130
+
131
+ 针对单一模式的精确表达式类型。
132
+ 初始化时指定具体模式名称,验证时仅匹配该模式。
133
+
134
+ 使用场景:
135
+ - 需要限制列只能使用特定表达式模式时
136
+ """
137
+
138
+ def __init__(self, registry: ExpressionRegistry, pattern: str) -> None:
139
+ """
140
+ @methoddesc 初始化特定表达式类型
141
+
142
+ 参数:
143
+ registry: 表达式注册表
144
+ pattern: 指定的模式名称
145
+
146
+ 抛出:
147
+ ValueError: 当注册表中未找到指定模式时抛出
148
+ """
149
+ self.registry = registry
150
+ self.pattern_name = pattern
151
+ specific_pattern: ExpressionPattern | None = None
152
+ for p in registry._patterns:
153
+ if p.name == pattern:
154
+ specific_pattern = p
155
+ break
156
+ if specific_pattern is None:
157
+ raise ValueError(f"在注册表中未找到指定的模式: '{pattern}'")
158
+ if isinstance(specific_pattern.regex, str):
159
+ import re
160
+
161
+ specific_pattern.regex = re.compile(specific_pattern.regex)
162
+ self.specific_pattern = specific_pattern
163
+
164
+ def validate(self, value: Any) -> tuple[bool, Any]:
165
+ """
166
+ @methoddesc 验证特定表达式字符串
167
+
168
+ 使用指定的模式进行精确匹配。
169
+
170
+ 参数:
171
+ value: 要验证的字符串
172
+
173
+ 返回:
174
+ 元组 (is_valid, error_message)
175
+ """
176
+ if not isinstance(value, str):
177
+ return False, f"期望是字符串, 但得到了 {type(value).__name__}。"
178
+ match = self.specific_pattern.regex.fullmatch(value.strip())
179
+ if not match:
180
+ return False, f"'{value}' 不匹配指定的表达式格式 '{self.pattern_name}'。"
181
+ try:
182
+ self.specific_pattern.parser_func(match.groupdict())
183
+ return True, None
184
+ except (ValueError, KeyError) as e:
185
+ return False, f"表达式 '{value}' 格式正确但内容无效: {e}"
186
+
187
+ def parse(self, value: Any) -> dict[str, Any]:
188
+ """
189
+ @methoddesc 解析特定表达式字符串
190
+
191
+ 使用指定的模式进行精确匹配并解析。
192
+
193
+ 参数:
194
+ value: 要解析的字符串
195
+
196
+ 返回:
197
+ 包含 type 和 value 的字典
198
+ """
199
+ match = self.specific_pattern.regex.fullmatch(value.strip())
200
+ if not match:
201
+ return {"type": self.specific_pattern.name, "value": None}
202
+ return {"type": self.specific_pattern.name, "value": self.specific_pattern.parser_func(match.groupdict())}