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,138 @@
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
+ """@fileoverview 文件路径安全校验
17
+
18
+ 功能概述:
19
+ - 提供统一的文件路径安全校验工具,供所有涉及文件系统操作的 HTTP 端点使用
20
+ - 两层校验策略:
21
+ 1. assert_no_traversal: 反穿越校验(拒绝 `..` 相对成分 + resolve 解析 symlink),
22
+ 适用于用户在项目外选择的数据文件场景(preview/validation/files-ops),保留任意数据文件能力
23
+ 2. assert_path_within_root: 限定到指定根目录(白名单语义),
24
+ 适用于临时文件(TEMP_DIR)、校验历史(configPath)等有明确归属的场景
25
+
26
+ 设计原则:
27
+ - 只做必要的防御:拒绝 `..` 穿越和 resolve 符号链接。不做系统目录黑名单之类的
28
+ 额外层——应用层重复 OS 权限边界既不完整(挡不住 ~/.ssh)又增加误伤与维护负担。
29
+ - 校验失败统一抛 HTTPException(403 拒绝 / 400 参数错误 / 404 不存在)
30
+ - 基于 Path.resolve() 解析符号链接与 `..`,避免 abspath+normpath 无法防 symlink 的缺陷
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import os
36
+ from pathlib import Path
37
+
38
+ from fastapi import HTTPException
39
+
40
+
41
+ def assert_no_traversal(file_path: str, *, must_exist: bool = True) -> str:
42
+ """
43
+ @methoddesc 反穿越校验,不限根目录
44
+
45
+ 适用于用户在项目外选择的数据文件场景(preview/validation/files-ops)。
46
+ 保留访问任意合法数据文件的能力,仅阻止路径穿越(`..` 相对成分)。
47
+
48
+ 参数:
49
+ file_path: 待校验的文件路径(原始字符串)
50
+ must_exist: 是否要求文件必须存在(默认 True)
51
+
52
+ 返回:
53
+ 解析后的绝对路径字符串
54
+
55
+ 抛出:
56
+ HTTPException: 400 路径为空/含穿越成分; 404 文件不存在
57
+ """
58
+ if not file_path or not file_path.strip():
59
+ raise HTTPException(status_code=400, detail="文件路径不能为空")
60
+
61
+ raw = file_path.strip()
62
+
63
+ # 拒绝显式 `..` 段:数据文件访问场景不应需要 `..`,用户应直接提供目标文件绝对路径
64
+ path_obj = Path(raw)
65
+ if any(part == ".." for part in path_obj.parts):
66
+ raise HTTPException(
67
+ status_code=400,
68
+ detail="路径包含 `..` 相对穿越成分,请提供目标文件的绝对路径。",
69
+ )
70
+
71
+ # resolve() 解析符号链接与剩余相对成分,得到真实绝对路径
72
+ try:
73
+ resolved = path_obj.resolve()
74
+ except OSError as e:
75
+ raise HTTPException(status_code=400, detail=f"路径解析失败: {e}")
76
+
77
+ if must_exist and not resolved.exists():
78
+ raise HTTPException(status_code=404, detail=f"文件未找到: {file_path}")
79
+
80
+ return str(resolved)
81
+
82
+
83
+ def assert_path_within_root(file_path: str, root: str, *, must_exist: bool = True) -> str:
84
+ """
85
+ @methoddesc 限定路径必须落在指定根目录内(白名单语义)
86
+
87
+ 适用于临时文件(TEMP_DIR)、校验历史(configPath)等有明确归属的场景。
88
+ 基于 resolve() 解析后做包含关系检查,能正确处理符号链接。
89
+
90
+ 参数:
91
+ file_path: 待校验的文件路径(原始字符串)
92
+ root: 允许的根目录绝对路径
93
+ must_exist: 是否要求文件必须存在(默认 True)
94
+
95
+ 返回:
96
+ 解析后的绝对路径字符串
97
+
98
+ 抛出:
99
+ HTTPException: 400 路径为空; 403 越出根目录; 404 不存在
100
+ """
101
+ if not file_path or not file_path.strip():
102
+ raise HTTPException(status_code=400, detail="文件路径不能为空")
103
+
104
+ raw = file_path.strip()
105
+ try:
106
+ resolved = Path(raw).resolve()
107
+ except OSError as e:
108
+ raise HTTPException(status_code=400, detail=f"路径解析失败: {e}")
109
+
110
+ try:
111
+ root_resolved = Path(root).resolve()
112
+ except OSError as e:
113
+ raise HTTPException(status_code=500, detail=f"根目录解析失败: {e}")
114
+
115
+ root_str = str(root_resolved)
116
+ resolved_str = str(resolved)
117
+ # 包含关系:resolved == root 或 resolved 在 root 之下
118
+ if resolved_str != root_str and not resolved_str.startswith(root_str + os.sep):
119
+ raise HTTPException(status_code=403, detail="路径越出允许的根目录范围。")
120
+
121
+ if must_exist and not resolved.exists():
122
+ raise HTTPException(status_code=404, detail=f"文件未找到: {file_path}")
123
+
124
+ return resolved_str
125
+
126
+
127
+ def validate_file_access(file_path: str) -> str:
128
+ """
129
+ @methoddesc 文件路径可访问性校验(反穿越硬化版)
130
+
131
+ 兼容历史调用点(preview/content_mode、path_mode、validation 等),
132
+ 内部委托给 assert_no_traversal。返回解析后的绝对路径供调用方使用。
133
+
134
+ 抛出:
135
+ HTTPException: 400 路径为空/含 `..` 穿越成分/解析失败; 404 文件不存在
136
+ (本函数不做根目录限定,不会抛 403;403 仅由 assert_path_within_root 抛出)
137
+ """
138
+ return assert_no_traversal(file_path, must_exist=True)
@@ -0,0 +1,57 @@
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
+ - 负责将 core 层加载的配置文件对象转换为 domain 层运行时数据集 Schema
21
+ - 作为 core 层与 domain 层之间的转换桥梁,避免 core 层反向依赖 domain
22
+
23
+ 架构设计:
24
+ - 转换层: core (TableSchemaFile / ConstraintFile) -> domain (DataSetSchema)
25
+ - 由 core.project.loader 的主加载流程调用
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ from typing import Any
31
+
32
+ from app.shared.core.project.constraint.factory import create_constraints
33
+ from app.shared.core.project.schema.types import TableSchemaFile
34
+ from app.shared.domain import DataSetSchema
35
+
36
+ from .schema_runtime_builder import build_runtime_schemas
37
+
38
+
39
+ def build_dataset_schema(
40
+ schema_files: dict[str, TableSchemaFile],
41
+ constraint_files: dict[str, Any],
42
+ registries: dict[str, Any],
43
+ ) -> tuple[DataSetSchema, list[str]]:
44
+ """@methoddesc 构建 domain 层的 DataSetSchema 运行时对象。
45
+
46
+ 将 core 层加载的 TableSchemaFile 和 ConstraintFile 转换为 domain 层的
47
+ TableSchema 列表和约束实例,最终组装为 DataSetSchema。
48
+
49
+ :param schema_files: schema 配置文件字典(table_id -> TableSchemaFile)
50
+ :param constraint_files: constraint 配置文件字典(constraint_id -> ConstraintFile)
51
+ :param registries: 表达式注册表等辅助对象
52
+ :return: (DataSetSchema, 约束构建过程中的警告列表)
53
+ """
54
+ runtime_tables = build_runtime_schemas(schema_files, registries)
55
+ runtime_constraints, warnings = create_constraints(constraint_files, schema_files)
56
+ dataset_schema = DataSetSchema(tables=runtime_tables, constraints=runtime_constraints)
57
+ return dataset_schema, warnings
@@ -0,0 +1,260 @@
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
+ """@fileoverview 数据文件 → Schema YAML 推断服务(headless infer_schema)
17
+
18
+ 功能概述:
19
+ - 读取 CSV/Excel/JSON 数据文件的头部样本,推断每列的 V2 数据类型
20
+ - 产出可直接落盘的 schema 文件内容(dict 形式,YAML 序列化由调用方决定)
21
+ - 供 CLI 子命令 `precis infer-schema` 与 MCP tool `infer_schema` 复用
22
+
23
+ 推断规则(按列,忽略空值):
24
+ - 全部可解析为整数 → integer
25
+ - 全部可解析为有限浮点(含整数)→ float
26
+ - 全部为 true/false 字面量 → boolean
27
+ - 全部为 ISO 日期(YYYY-MM-DD)→ date
28
+ - 其余(含混合类型、全空列)→ string
29
+
30
+ 设计说明:
31
+ - CSV/Excel 以 dtype=str 读入,避免 pandas 预转换掩盖原始格式
32
+ ("1.0" 与 "1" 的区别、日期字符串等);JSON 值为原生类型,
33
+ 分类器同时接受 str 与原生标量
34
+ - 只采样头部(默认 1000 行)控制大文件成本;类型冲突按保守回退 string
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ import json
40
+ import math
41
+ import uuid
42
+ from datetime import date
43
+ from pathlib import Path
44
+ from typing import Any
45
+
46
+ import pandas as pd
47
+
48
+ # V2 支持的 6 种数据类型(与 backend schema 类型定义一致)
49
+ DATA_TYPES = ("string", "integer", "float", "decimal", "boolean", "date")
50
+
51
+ # 支持的输入扩展名
52
+ _CSV_EXTS = {".csv"}
53
+ _EXCEL_EXTS = {".xlsx", ".xls"}
54
+ _JSON_EXTS = {".json", ".jsonl"}
55
+
56
+ # boolean 字面量集合(大小写不敏感)
57
+ _BOOL_LITERALS = {"true", "false"}
58
+
59
+ # 默认采样行数
60
+ DEFAULT_SAMPLE_ROWS = 1000
61
+
62
+
63
+ def _classify_value(value: Any) -> str | None:
64
+ """对单个非空值做候选类型判定。
65
+
66
+ Args:
67
+ value: 单元格值(str 或 JSON 原生标量)
68
+
69
+ Returns:
70
+ 候选类型(string/integer/float/boolean/date);无法判定返回 string
71
+ """
72
+ if isinstance(value, bool):
73
+ return "boolean"
74
+ if isinstance(value, int):
75
+ return "integer"
76
+ if isinstance(value, float):
77
+ # 非有限浮点(NaN/Inf)不能作为类型证据
78
+ return "float" if math.isfinite(value) else "string"
79
+
80
+ text = str(value).strip()
81
+ if not text:
82
+ return None
83
+ if text.lower() in _BOOL_LITERALS:
84
+ return "boolean"
85
+ # 整数:可选符号的纯数字
86
+ if _is_int_text(text):
87
+ return "integer"
88
+ # 浮点:排除 nan/inf 等非有限字面量
89
+ try:
90
+ parsed = float(text)
91
+ if math.isfinite(parsed):
92
+ return "float"
93
+ except ValueError:
94
+ pass
95
+ # 日期:严格 ISO 格式(YYYY-MM-DD)
96
+ try:
97
+ date.fromisoformat(text)
98
+ return "date"
99
+ except ValueError:
100
+ pass
101
+ return "string"
102
+
103
+
104
+ def _is_int_text(text: str) -> bool:
105
+ """判断字符串是否为整数文本(允许前后符号)。"""
106
+ body = text[1:] if text[:1] in "+-" else text
107
+ return body.isdigit() and len(body) > 0
108
+
109
+
110
+ def infer_column_type(values: list[Any], dominant_ratio: float = 0.9) -> str:
111
+ """对一列采样值推断 V2 类型。
112
+
113
+ 规则:
114
+ 1. 全部非空值同属一类 → 该类型(integer/float 合并时 float 优先提升)
115
+ 2. 否则若单一数值/日期/布尔候选占比 >= dominant_ratio → 采信该候选
116
+ (数据中的少量脏值正是校验要抓的问题,不应把整列拖回 string)
117
+ 3. 其余(含混合类型、全空列)→ string
118
+
119
+ Args:
120
+ values: 该列的非空采样值列表
121
+ dominant_ratio: 主导类型采信阈值(占非空值的比例)
122
+
123
+ Returns:
124
+ 6 种 V2 类型之一;空列表(全空列)返回 string(安全默认)
125
+ """
126
+ counts: dict[str, int] = {}
127
+ total = 0
128
+ for value in values:
129
+ candidate = _classify_value(value)
130
+ if candidate is None:
131
+ continue
132
+ total += 1
133
+ counts[candidate] = counts.get(candidate, 0) + 1
134
+ if total == 0:
135
+ return "string"
136
+
137
+ def _ratio(kind: str) -> float:
138
+ return counts.get(kind, 0) / total
139
+
140
+ # 数值合并视角:integer 与 float 同属数值,浮点出现则整列提升 float
141
+ numeric_count = counts.get("integer", 0) + counts.get("float", 0)
142
+ if numeric_count == total:
143
+ return "float" if counts.get("float") else "integer"
144
+ if _ratio("boolean") == 1.0:
145
+ return "boolean"
146
+ if _ratio("date") == 1.0:
147
+ return "date"
148
+
149
+ # 主导采信:少量脏值不拖垮整列(如 17 行里 1 个非法日期)
150
+ if numeric_count >= dominant_ratio * total:
151
+ return "float" if counts.get("float") else "integer"
152
+ for kind in ("boolean", "date"):
153
+ if _ratio(kind) >= dominant_ratio:
154
+ return kind
155
+ return "string"
156
+
157
+
158
+ def _read_head(data_file: Path, sample_rows: int) -> pd.DataFrame:
159
+ """按扩展名读取数据文件头部样本。
160
+
161
+ CSV/Excel 以 dtype=str 读入保留原始格式;JSON 解析原生类型。
162
+ JSONL(.jsonl / 逐行 JSON 对象)按行解析。
163
+
164
+ Args:
165
+ data_file: 数据文件路径
166
+ sample_rows: 采样行数上限
167
+
168
+ Returns:
169
+ 采样 DataFrame(空数据返回空 DataFrame)
170
+
171
+ Raises:
172
+ ValueError: 文件不存在或不支持的扩展名
173
+ """
174
+ if not data_file.exists():
175
+ raise ValueError(f"数据文件不存在: {data_file}")
176
+
177
+ suffix = data_file.suffix.lower()
178
+ try:
179
+ if suffix in _CSV_EXTS:
180
+ return pd.read_csv(data_file, dtype=str, nrows=sample_rows, keep_default_na=True)
181
+ if suffix in _EXCEL_EXTS:
182
+ # Excel 不支持按行采样,整体读入后取头部(大文件场景后续再优化)
183
+ df = pd.read_excel(data_file, dtype=str)
184
+ return df.head(sample_rows)
185
+ if suffix in _JSON_EXTS:
186
+ return _read_json_head(data_file, sample_rows)
187
+ except pd.errors.EmptyDataError as e:
188
+ raise ValueError(f"数据文件无表头或无数据行,无法推断 schema: {data_file}") from e
189
+ raise ValueError(f"不支持的文件扩展名: {suffix}(支持 CSV/Excel/JSON)")
190
+
191
+
192
+ def _read_json_head(data_file: Path, sample_rows: int) -> pd.DataFrame:
193
+ """读取 JSON(对象数组)或 JSONL(逐行对象)头部样本。"""
194
+ text = data_file.read_text(encoding="utf-8")
195
+ if data_file.suffix.lower() == ".jsonl":
196
+ records = []
197
+ for line in text.splitlines():
198
+ line = line.strip()
199
+ if line:
200
+ records.append(json.loads(line))
201
+ return pd.DataFrame(records).head(sample_rows)
202
+ data = json.loads(text)
203
+ if isinstance(data, dict):
204
+ data = [data]
205
+ if not isinstance(data, list):
206
+ raise ValueError(f"JSON 顶层必须是对象数组: {data_file}")
207
+ return pd.DataFrame(data).head(sample_rows)
208
+
209
+
210
+ def infer_schema(
211
+ data_file: str | Path,
212
+ sample_rows: int = DEFAULT_SAMPLE_ROWS,
213
+ table_id: str | None = None,
214
+ table_name: str | None = None,
215
+ source_path: str | None = None,
216
+ ) -> dict[str, Any]:
217
+ """推断数据文件的 V2 schema 结构。
218
+
219
+ Args:
220
+ data_file: 数据文件路径
221
+ sample_rows: 类型推断的采样行数上限
222
+ table_id: 表 ID(默认生成 UUID v4;替换既有 schema 时传入原 id 保持引用不变)
223
+ table_name: 表显示名(默认取文件名去扩展名)
224
+ source_path: 写入 schema 的 source.path(默认为 data_file 原样路径)
225
+
226
+ Returns:
227
+ schema 文件结构的字典(version/id/name/source/columns),
228
+ 经 yaml.safe_dump 即可落盘为合法 *.schema.yaml
229
+
230
+ Raises:
231
+ ValueError: 文件不存在、扩展名不支持或无任何列可推断
232
+ """
233
+ path = Path(data_file)
234
+ df = _read_head(path, sample_rows)
235
+
236
+ if df.empty or len(df.columns) == 0:
237
+ raise ValueError(f"数据文件无表头或无数据行,无法推断 schema: {data_file}")
238
+
239
+ columns: list[dict[str, Any]] = []
240
+ for col in df.columns:
241
+ # 逐列收集非空采样值后推断类型
242
+ non_null = [v for v in df[col].tolist() if v is not None and not (isinstance(v, float) and math.isnan(v))]
243
+ columns.append(
244
+ {
245
+ "id": str(col),
246
+ "name": str(col),
247
+ "type": infer_column_type(non_null),
248
+ }
249
+ )
250
+
251
+ return {
252
+ "version": 2,
253
+ "id": table_id or str(uuid.uuid4()),
254
+ "name": table_name or path.stem,
255
+ "source": {
256
+ "mode": "relative_file",
257
+ "path": source_path if source_path is not None else str(data_file),
258
+ },
259
+ "columns": columns,
260
+ }
@@ -0,0 +1,120 @@
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 Schema 运行时对象构建服务
18
+
19
+ 功能概述:
20
+ - 将 core 层的 TableSchemaFile 配置转换为 domain 层的运行时 TableSchema 对象
21
+ - 负责 core DTO 与 domain model 之间的转换,避免 core 层直接依赖 domain
22
+
23
+ 架构设计:
24
+ - 转换层: config (TableSchemaFile) -> domain (TableSchema/ColumnSchema)
25
+ - 依赖 domain 的 build_type_from_config 解析列类型
26
+ - 由 services 层(如 project_loader、validation)调用
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from typing import Any
32
+
33
+ from app.shared.core.project.schema.types import TableSchemaFile
34
+ from app.shared.domain import ColumnSchema, TableSchema, build_type_from_config
35
+
36
+
37
+ def build_runtime_schema(schema_file: TableSchemaFile, registries: dict[str, Any]) -> TableSchema:
38
+ """@methoddesc 从 TableSchemaFile 构建运行时 TableSchema 对象。
39
+
40
+ 本函数位于 services 层,负责将 core 层的配置文件对象转换为 domain 层运行时对象,
41
+ 从而消除 core.project.schema.reader 对 domain 的反向依赖。
42
+
43
+ :param schema_file: TableSchemaFile 配置对象
44
+ :param registries: 包含 expression_registry 的字典
45
+ :return: 运行时 TableSchema 对象
46
+ """
47
+ columns: list[ColumnSchema] = []
48
+ for col in schema_file.columns:
49
+ data_type = build_type_from_config(col.type, registries)
50
+ col_schema = ColumnSchema(
51
+ name=col.name,
52
+ data_type=data_type,
53
+ is_primary_key=col.primary_key,
54
+ expand=col.expand,
55
+ nullable=col.nullable,
56
+ id=col.id,
57
+ json_path=col.json_path,
58
+ )
59
+ if col.children:
60
+ child_columns = []
61
+ for child in col.children:
62
+ child_data_type = build_type_from_config(child.type, registries)
63
+ child_columns.append(
64
+ ColumnSchema(
65
+ name=child.name,
66
+ data_type=child_data_type,
67
+ is_primary_key=child.primary_key,
68
+ expand=child.expand,
69
+ nullable=child.nullable,
70
+ id=child.id,
71
+ json_path=child.json_path,
72
+ )
73
+ )
74
+ col_schema.children = child_columns
75
+ columns.append(col_schema)
76
+
77
+ # 提取数据源信息
78
+ source_file = None
79
+ sheet_name = None
80
+ header_row = 0
81
+ source_config: dict[str, Any] = {}
82
+ if schema_file.source:
83
+ source_file = schema_file.source.path
84
+ sheet_name = schema_file.source.sheet
85
+ header_row = schema_file.source.header_row
86
+ # 保存完整的 source 配置,用于数据加载
87
+ source_config = schema_file.source.to_loader_config()
88
+ elif schema_file.sheet:
89
+ # 向后兼容:无 source 时回退到顶层 sheet 字段
90
+ # (source.sheet 与顶层 sheet 互斥,但单独的顶层 sheet 是合法配置)
91
+ sheet_name = schema_file.sheet
92
+
93
+ # 构建运行时 TableSchema 对象
94
+ return TableSchema(
95
+ id=schema_file.id,
96
+ name=schema_file.name,
97
+ columns=columns,
98
+ source_file=source_file,
99
+ sheet_name=sheet_name,
100
+ header_row=header_row,
101
+ script_checks=schema_file.script_checks,
102
+ source_config=source_config,
103
+ )
104
+
105
+
106
+ def build_runtime_schemas(
107
+ schema_files: dict[str, TableSchemaFile],
108
+ registries: dict[str, Any],
109
+ ) -> dict[str, TableSchema]:
110
+ """@methoddesc 批量构建运行时 TableSchema 对象。
111
+
112
+ :param schema_files: schema 文件字典(table_id -> TableSchemaFile)
113
+ :param registries: 表达式注册表
114
+ :return: 运行时 TableSchema 字典(table_id -> TableSchema)
115
+ """
116
+ tables: dict[str, TableSchema] = {}
117
+ for table_id, schema in schema_files.items():
118
+ # 使用 table_id 作为键,与新的文件命名规则保持一致
119
+ tables[table_id] = build_runtime_schema(schema, registries)
120
+ return tables
@@ -0,0 +1,52 @@
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
+ - 提供 ValidationType、ValidationResult 等核心类型
22
+ - 提供 UnifiedValidationService 统一校验服务
23
+ - 提供文件加载便捷函数
24
+ """
25
+
26
+ from .chunked_loader import ChunkedDataLoader, ChunkedValidationResult
27
+ from .loader import load_file_data
28
+ from .memory_monitor import MemoryMonitor
29
+ from .service import UnifiedValidationService
30
+ from .types import ValidationResult, ValidationType
31
+ from .validators import (
32
+ BaseValidator,
33
+ CompositeValidator,
34
+ ConstraintAdapter,
35
+ DateLogicValidator,
36
+ PreCheck,
37
+ )
38
+
39
+ __all__ = [
40
+ "ValidationType",
41
+ "ValidationResult",
42
+ "UnifiedValidationService",
43
+ "load_file_data",
44
+ "BaseValidator",
45
+ "ConstraintAdapter",
46
+ "PreCheck",
47
+ "CompositeValidator",
48
+ "DateLogicValidator",
49
+ "MemoryMonitor",
50
+ "ChunkedDataLoader",
51
+ "ChunkedValidationResult",
52
+ ]