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,252 @@
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
+ - 封装文件路径、编码、表头等通用配置
22
+ - 实现路径验证(非空、非法字符检查)
23
+ - 提供相对/绝对路径的连接键生成
24
+
25
+ 架构设计:
26
+ - 继承自 DataSourceSpec,扩展文件相关配置
27
+ - 子类覆盖 type 和 source_type 即可支持新文件格式
28
+ - field_validator 自动校验 path 字段合法性
29
+ - 与文件加载器(ExcelLoader、CSVLoader 等)配对使用
30
+ - FileValidationMixin 提供文件存在性、可读性、大小等通用验证
31
+
32
+ 输入示例:
33
+ spec = FileSourceSpec(
34
+ path="data/users.csv",
35
+ mode="relative",
36
+ encoding="utf-8",
37
+ header_enabled=True,
38
+ header_row=0,
39
+ skip_rows=0
40
+ )
41
+
42
+ 输出示例:
43
+ key = spec.get_connection_key() # 返回绝对路径字符串
44
+ display = spec.to_display_dict() # 返回可展示的字典
45
+ """
46
+
47
+ from __future__ import annotations
48
+
49
+ from abc import ABC
50
+ from pathlib import Path
51
+ from typing import Any, ClassVar, Literal
52
+
53
+ from pydantic import Field, field_validator
54
+
55
+ from .base import DataSourceSpec
56
+
57
+
58
+ class FileSourceSpec(DataSourceSpec, ABC):
59
+ """
60
+ @classdesc 文件数据源基类
61
+
62
+ 所有基于文件的数据源(Excel、CSV、JSON 等)的基类。
63
+ 提供文件相关的通用配置和验证,子类只需覆盖 type 和 source_type 即可。
64
+
65
+ 属性:
66
+ path: 文件路径(相对或绝对)
67
+ mode: 路径模式,"relative" 表示相对于项目根目录,"absolute" 表示绝对路径
68
+ encoding: 文件编码(默认 utf-8),常用于 CSV 和 JSON 文件
69
+ header_enabled: 是否有表头行,True 表示第一行(或 header_row 指定行)是列名
70
+ header_row: 表头行索引(从0开始),默认第0行
71
+ skip_rows: 跳过的行数(表头前),用于跳过文件开头的说明行
72
+ nrows: 读取的最大行数,None 表示读取全部数据
73
+
74
+ 示例:
75
+ >>> spec = FileSourceSpec(path="data/users.csv", mode="relative")
76
+ >>> spec.get_connection_key() # 返回解析后的绝对路径
77
+ """
78
+
79
+ # 类级别的类型标识
80
+ source_type: ClassVar[str] = "file"
81
+
82
+ # 实例级别的类型字段(必须与 source_type 匹配)
83
+ type: str = "file" # 子类应覆盖此值
84
+
85
+ # 文件相关配置
86
+ path: str = Field(..., description="文件路径(相对或绝对)")
87
+ mode: Literal["relative", "absolute"] = Field("relative", description="路径模式")
88
+ encoding: str = Field("utf-8", description="文件编码")
89
+
90
+ # 表头配置
91
+ header_enabled: bool = Field(True, description="是否有表头行")
92
+ header_row: int = Field(0, ge=0, description="表头行索引(从0开始)")
93
+
94
+ # 数据范围配置
95
+ skip_rows: int = Field(0, ge=0, description="跳过的行数(表头前)")
96
+ nrows: int | None = Field(None, ge=1, description="读取的最大行数")
97
+
98
+ @field_validator("path")
99
+ @classmethod
100
+ def validate_path(cls, v: str) -> str:
101
+ """
102
+ @methoddesc 验证路径非空且不含非法字符
103
+
104
+ 这是 Pydantic 的字段验证器,在创建实例时自动执行。
105
+ 会去除首尾空白字符,并检查 Windows 非法字符。
106
+
107
+ Args:
108
+ v: 用户输入的路径字符串
109
+
110
+ Returns:
111
+ 去除首尾空白后的路径字符串
112
+
113
+ Raises:
114
+ ValueError: 路径为空或包含 Windows 非法字符时抛出
115
+ """
116
+ # 检查路径是否为空或仅包含空白字符
117
+ if not v or not v.strip():
118
+ raise ValueError("文件路径不能为空")
119
+
120
+ normalized_path = v.strip()
121
+
122
+ # 检查非法字符。冒号需要单独处理,Windows 绝对路径盘符(如 D:\foo.csv)
123
+ # 是合法输入,不能被通用非法字符规则误杀。
124
+ illegal_chars = '<>"|?*'
125
+ if any(c in normalized_path for c in illegal_chars):
126
+ raise ValueError(f"文件路径包含非法字符: {illegal_chars}")
127
+
128
+ colon_count = normalized_path.count(":")
129
+ if colon_count:
130
+ is_windows_absolute_path = (
131
+ colon_count == 1
132
+ and len(normalized_path) >= 3
133
+ and normalized_path[0].isalpha()
134
+ and normalized_path[1] == ":"
135
+ and normalized_path[2] in ("\\", "/")
136
+ )
137
+ if not is_windows_absolute_path:
138
+ raise ValueError("文件路径包含非法字符: :")
139
+
140
+ # 去除首尾空白,避免因为空格导致文件找不到
141
+ return normalized_path
142
+
143
+ def get_connection_key(self) -> str:
144
+ """
145
+ @methoddesc 获取连接标识符
146
+
147
+ 对于文件数据源,使用绝对路径作为唯一标识。
148
+ 如果 mode 为 "relative",会解析为绝对路径(基于当前工作目录)。
149
+
150
+ Returns:
151
+ 文件的绝对路径字符串
152
+ """
153
+ # 如果已经是绝对路径,直接返回
154
+ if self.mode == "absolute":
155
+ return self.path
156
+
157
+ # 相对路径需要解析为绝对路径
158
+ # 注意:实际使用时需要基于项目根目录解析
159
+ # Path.resolve() 会将相对路径转换为绝对路径,并解析 . 和 ..
160
+ return str(Path(self.path).resolve())
161
+
162
+ def get_file_extension(self) -> str:
163
+ """
164
+ @methoddesc 获取文件扩展名(小写)
165
+
166
+ 用于根据扩展名判断文件类型,或验证文件格式是否符合预期。
167
+
168
+ Returns:
169
+ 小写的文件扩展名,如 ".csv"、".xlsx"、".json"
170
+ """
171
+ return Path(self.path).suffix.lower()
172
+
173
+ def to_display_dict(self) -> dict[str, Any]:
174
+ """
175
+ @methoddesc 转换为显示用的字典
176
+
177
+ 在基类的基础上增加文件相关的展示字段,方便前端显示文件信息。
178
+
179
+ Returns:
180
+ 包含 type、name、path、mode、encoding、extension 等字段的字典
181
+ """
182
+ return {
183
+ **super().to_display_dict(), # 复用基类的通用字段
184
+ "path": self.path,
185
+ "mode": self.mode,
186
+ "encoding": self.encoding,
187
+ "extension": self.get_file_extension(),
188
+ }
189
+
190
+
191
+ class FileValidationMixin:
192
+ """
193
+ @classdesc 文件验证混入类
194
+
195
+ 提供文件数据源通用的验证方法,如检查文件是否存在、是否可读、文件大小等。
196
+ 可以通过多重继承混入到具体的 Spec 类中使用。
197
+ 所有方法返回 str | None:如果验证通过返回 None,失败返回错误信息字符串。
198
+ """
199
+
200
+ path: str # 由混入目标类提供
201
+
202
+ def validate_file_exists(self) -> str | None:
203
+ """
204
+ @methoddesc 验证文件是否存在
205
+
206
+ Returns:
207
+ 如果文件不存在返回错误信息,否则返回 None
208
+ """
209
+ path = Path(self.path)
210
+ if not path.exists():
211
+ return f"文件不存在: {self.path}"
212
+ return None
213
+
214
+ def validate_file_readable(self) -> str | None:
215
+ """
216
+ @methoddesc 验证文件是否可读
217
+
218
+ 先检查路径是否存在且是文件(而非目录),
219
+ 实际的读取权限测试在数据加载时进行。
220
+
221
+ Returns:
222
+ 如果路径不是文件返回错误信息,否则返回 None
223
+ """
224
+ path = Path(self.path)
225
+ # 检查路径存在但不是文件(可能是目录)
226
+ if path.exists() and not path.is_file():
227
+ return f"路径不是文件: {self.path}"
228
+ # 实际读取测试在加载时进行(避免在这里做 I/O 操作影响性能)
229
+ return None
230
+
231
+ def validate_file_size(self, max_size_mb: float = 500) -> str | None:
232
+ """
233
+ @methoddesc 验证文件大小
234
+
235
+ 检查文件是否超过指定大小,用于提前预警大文件可能导致加载缓慢。
236
+
237
+ Args:
238
+ max_size_mb: 最大允许大小(MB),默认 500MB
239
+
240
+ Returns:
241
+ 警告信息(如果文件过大),或 None(如果正常或文件不存在)
242
+ """
243
+ path = Path(self.path)
244
+ # 如果文件不存在,跳过大小检查(存在性检查由 validate_file_exists 负责)
245
+ if not path.exists():
246
+ return None
247
+
248
+ # 计算文件大小(MB),1 MB = 1024 * 1024 字节
249
+ size_mb = path.stat().st_size / (1024 * 1024)
250
+ if size_mb > max_size_mb:
251
+ return f"警告: 文件较大 ({size_mb:.1f}MB),加载可能较慢"
252
+ return None
@@ -0,0 +1,226 @@
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 JSON 数据源规范模块
18
+
19
+ 功能概述:
20
+ - 定义 JSON 文件的数据源规格类
21
+ - 支持多种 JSON 格式:对象数组、JSON Lines、嵌套对象
22
+ - 提供 JSONPath 提取和自动展平功能
23
+ - 支持流式读取大文件和嵌套深度控制
24
+
25
+ 架构设计:
26
+ - 继承自 FileSourceSpec,复用文件通用配置
27
+ - 通过 @register_source_spec 装饰器自动注册到规格注册表
28
+ - 与 JSONLoader 配对使用,通过 format 字段选择解析策略
29
+ - model_validator 自动校验 json_path 格式和必要性
30
+
31
+ 输入示例:
32
+ # 对象数组
33
+ spec = JSONSourceSpec(path="data/users.json", format="array")
34
+ # 嵌套对象提取
35
+ spec = JSONSourceSpec(
36
+ path="data/api.json",
37
+ format="object",
38
+ json_path="$.data.items",
39
+ sep="."
40
+ )
41
+ # JSON Lines
42
+ spec = JSONSourceSpec(path="data/large.jsonl", format="lines")
43
+
44
+ 输出示例:
45
+ loader_class = spec.get_loader_class() # 返回 JSONLoader
46
+ display = spec.to_display_dict()
47
+ # {"path": "data/api.json", "format": "object", "json_path": "$.data.items", ...}
48
+ """
49
+
50
+ from __future__ import annotations
51
+
52
+ import builtins
53
+ from typing import TYPE_CHECKING, Any, ClassVar, Literal, Self
54
+
55
+ if TYPE_CHECKING:
56
+ from ..loaders.base import DataSourceLoader
57
+
58
+ from pydantic import Field, model_validator
59
+
60
+ from .base import register_source_spec
61
+ from .file_base import FileSourceSpec
62
+
63
+
64
+ @register_source_spec
65
+ class JSONSourceSpec(FileSourceSpec):
66
+ """
67
+ @classdesc JSON 数据源配置
68
+
69
+ 支持多种 JSON 格式:对象数组、JSON Lines、嵌套对象。
70
+ 提供 JSONPath 提取和自动展平功能,适应不同结构的 JSON 数据。
71
+
72
+ 特有配置:
73
+ format: JSON 格式类型
74
+ - auto: 自动检测格式
75
+ - array: 对象数组(如 [{"id": 1}, {"id": 2}])
76
+ - lines: JSON Lines(每行一个 JSON 对象)
77
+ - object: 嵌套对象(需要通过 json_path 提取数据)
78
+ json_path: JSONPath 提取路径(如 "$.data.users"),用于从嵌套结构中提取数据
79
+ record_path: 要展平的路径,用于将嵌套数组展开为行
80
+ meta_prefix: 元数据字段前缀,用于区分展平时的元数据字段
81
+ sep: 嵌套对象展平时的分隔符(如 "." 表示 "user.name")
82
+ dtype: 列类型指定,如 {"id": "str", "count": "int"}
83
+ flatten: 是否自动扁平化嵌套结构(将嵌套对象展开为平面表格)
84
+ max_depth: 扁平化时的最大递归深度,防止无限递归
85
+ streaming_threshold_mb: 启用流式读取的阈值(MB),超过则分块读取
86
+ chunk_size: 流式读取时的块大小(行数)
87
+
88
+ 示例:
89
+ # 对象数组
90
+ ```yaml
91
+ source:
92
+ type: json
93
+ path: data/users.json
94
+ format: array
95
+ ```
96
+
97
+ # 嵌套对象提取
98
+ ```yaml
99
+ source:
100
+ type: json
101
+ path: data/api_response.json
102
+ format: object
103
+ json_path: "$.data.items"
104
+ sep: "."
105
+ ```
106
+
107
+ # JSON Lines
108
+ ```yaml
109
+ source:
110
+ type: json
111
+ path: data/large_file.jsonl
112
+ format: lines
113
+ ```
114
+ """
115
+
116
+ source_type: ClassVar[str] = "json"
117
+ type: str = "json"
118
+
119
+ # JSON 格式类型(D8: auto 已废弃,必须显式指定 array/lines/object)
120
+ format: Literal["array", "lines", "object"] = Field(
121
+ ..., description="JSON 格式类型(必填):array-顶层数组, lines-JSON Lines, object-嵌套对象(需 json_path)"
122
+ )
123
+
124
+ # 数据提取路径
125
+ json_path: str | None = Field(None, description="JSONPath 提取路径,如 '$.data.items'(当 format=object 时必需)")
126
+
127
+ # 展平配置
128
+ record_path: str | None = Field(None, description="要展平的路径,如 'items'(用于数组字段展开为行)")
129
+ meta_prefix: str = Field("meta.", description="元数据字段前缀(展平时用于区分原始字段和元数据字段)")
130
+ sep: str = Field(".", description="嵌套对象展平时的分隔符(如 'user.name')")
131
+
132
+ # 类型推断
133
+ dtype: dict[str, str] | None = Field(None, description="列类型指定,如 {'id': 'str', 'count': 'int'}")
134
+
135
+ # 展平配置
136
+ flatten: bool = Field(False, description="是否自动扁平化嵌套结构(将多层嵌套转为单层表格)")
137
+ max_depth: int | None = Field(None, description="扁平化时的最大递归深度(None 表示不限制)")
138
+
139
+ # 大文件处理
140
+ streaming_threshold_mb: float = Field(50.0, ge=1.0, description="启用流式读取的阈值(MB),超过此大小将分块读取")
141
+ chunk_size: int = Field(10000, ge=100, description="流式读取时的块大小(每次读取的行数)")
142
+
143
+ @model_validator(mode="before")
144
+ @classmethod
145
+ def validate_format_not_auto(cls, data: Any) -> Any:
146
+ """D8: 拦截已废弃的 format='auto',给出可操作的迁移引导。
147
+
148
+ auto 会猜测主数据数组(取最长/取第一个),与精确列定义的设计矛盾,已废弃。
149
+ 存量配置里 format: auto 在此被捕获,抛出明确错误引导用户改成显式 format,
150
+ 而非让 Pydantic Literal 报含糊的"unexpected value"。
151
+ """
152
+ if isinstance(data, dict):
153
+ fmt = data.get("format")
154
+ if fmt == "auto":
155
+ raise ValueError(
156
+ "format='auto' 已废弃(自动检测会猜测主数据数组,可能导致校验跑错对象)。"
157
+ "请显式改为:array(顶层数组 [{...}])、lines(JSON Lines)、"
158
+ "或 object(嵌套对象,需配合 json_path 如 $.data)。"
159
+ )
160
+ return data
161
+
162
+ @model_validator(mode="after")
163
+ def validate_json_path(self) -> Self:
164
+ """
165
+ @methoddesc 当 format=object 时,json_path 是必需的
166
+
167
+ object 格式表示 JSON 是一个嵌套对象,需要通过 json_path 定位到数据数组。
168
+ 如果没有 json_path,加载器不知道从哪里提取数据。
169
+
170
+ Raises:
171
+ ValueError: 当 format="object" 但 json_path 为空时抛出
172
+
173
+ Returns:
174
+ 验证通过后的实例自身
175
+ """
176
+ if self.format == "object" and not self.json_path:
177
+ raise ValueError("当 format='object' 时,必须指定 json_path")
178
+ return self
179
+
180
+ @model_validator(mode="after")
181
+ def validate_json_path_format(self) -> Self:
182
+ """
183
+ @methoddesc 验证 json_path 格式
184
+
185
+ JSONPath 必须以 "$.") 开头,这是 JSONPath 标准的根元素标识。
186
+ 例如 "$.data.items" 表示从根对象开始,访问 data 属性下的 items 属性。
187
+
188
+ Raises:
189
+ ValueError: 当 json_path 不以 "$." 开头时抛出
190
+
191
+ Returns:
192
+ 验证通过后的实例自身
193
+ """
194
+ if self.json_path and not self.json_path.startswith("$."):
195
+ raise ValueError(f"json_path 必须以 '$.') 开头,当前值: '{self.json_path}'")
196
+ return self
197
+
198
+ def get_loader_class(self) -> builtins.type[DataSourceLoader]:
199
+ """
200
+ @methoddesc 获取 JSON 数据加载器类
201
+
202
+ 延迟导入 JSONLoader,避免循环依赖。
203
+ JSONLoader 根据 format 字段选择不同的解析策略。
204
+
205
+ Returns:
206
+ JSONLoader 类
207
+ """
208
+ from ..loaders.json_loader import JSONLoader
209
+
210
+ return JSONLoader
211
+
212
+ def to_display_dict(self) -> dict[str, Any]:
213
+ """
214
+ @methoddesc 转换为显示用的字典
215
+
216
+ 在基类的基础上增加 JSON 特有的展示字段。
217
+
218
+ Returns:
219
+ 包含 format、json_path、sep 等 JSON 特有字段的字典
220
+ """
221
+ return {
222
+ **super().to_display_dict(),
223
+ "format": self.format,
224
+ "json_path": self.json_path,
225
+ "sep": self.sep,
226
+ }
@@ -0,0 +1,161 @@
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 SQL 数据源规格模块
18
+
19
+ 功能概述:
20
+ - 定义 SQL 数据库连接的数据源规格类
21
+ - 支持连接字符串、表名或查询语句配置
22
+ - 提供查询参数和批量读取大小配置
23
+ - 连接信息展示时自动隐藏密码等敏感信息
24
+
25
+ 架构设计:
26
+ - 直接继承自 DataSourceSpec(非文件类数据源)
27
+ - 通过 @register_source_spec 装饰器自动注册到规格注册表
28
+ - 与 SQLLoader 配对使用,实现数据库数据加载
29
+ - 连接键由 connection_string 和 table_or_query 组合生成
30
+
31
+ 输入示例:
32
+ spec = SQLSourceSpec(
33
+ connection_string="postgresql://user:pass@localhost/db",
34
+ table_or_query="SELECT * FROM users WHERE age > 18",
35
+ params={"min_age": 18},
36
+ batch_size=1000
37
+ )
38
+
39
+ 输出示例:
40
+ key = spec.get_connection_key()
41
+ # "postgresql://user:pass@localhost/db:SELECT * FROM users WHERE age > 18"
42
+ display = spec.to_display_dict()
43
+ # {"connection": "postgresql://user:***@localhost/db", "table_or_query": "...", ...}
44
+ """
45
+
46
+ from __future__ import annotations
47
+
48
+ import builtins
49
+ from typing import TYPE_CHECKING, Any, ClassVar
50
+ from urllib.parse import urlparse, urlunparse
51
+
52
+ from pydantic import Field
53
+
54
+ from .base import DataSourceSpec, register_source_spec
55
+
56
+ if TYPE_CHECKING:
57
+ from ..loaders.base import DataSourceLoader
58
+
59
+
60
+ @register_source_spec
61
+ class SQLSourceSpec(DataSourceSpec):
62
+ """
63
+ @classdesc SQL 数据库数据源规格
64
+
65
+ 用于从 SQL 数据库(PostgreSQL、MySQL、SQLite 等)加载数据。
66
+ 支持通过完整查询语句或表名指定要读取的数据。
67
+
68
+ 属性:
69
+ connection_string: 数据库连接字符串(包含协议、用户名、密码、主机、数据库名)
70
+ table_or_query: 表名或 SQL 查询语句
71
+ params: 查询参数(用于参数化查询,防止 SQL 注入)
72
+ batch_size: 批量读取大小(每次从数据库读取的行数)
73
+
74
+ 示例:
75
+ ```yaml
76
+ source:
77
+ type: sql
78
+ connection_string: "postgresql://user:pass@localhost/mydb"
79
+ table_or_query: "SELECT * FROM users WHERE status = %(status)s"
80
+ params:
81
+ status: "active"
82
+ ```
83
+ """
84
+
85
+ source_type: ClassVar[str] = "sql"
86
+ type: str = "sql"
87
+
88
+ # 连接配置
89
+ connection_string: str = Field(..., description="数据库连接字符串(如 postgresql://user:pass@localhost/db)")
90
+ table_or_query: str = Field(..., description="表名(如 'users')或 SQL 查询语句(如 'SELECT * FROM users')")
91
+ params: dict[str, Any] | None = Field(None, description="查询参数(用于参数化查询,如 {'min_age': 18})")
92
+
93
+ # 性能配置
94
+ batch_size: int = Field(1000, ge=1, description="批量读取大小(每次从数据库读取的行数,用于控制内存使用)")
95
+
96
+ def get_connection_key(self) -> str:
97
+ """
98
+ @methoddesc 获取连接标识符
99
+
100
+ SQL 数据源的连接键由连接字符串和查询语句组合而成,
101
+ 确保同一个数据库的不同查询被视为不同的数据源。
102
+
103
+ Returns:
104
+ "连接字符串:查询语句" 格式的字符串
105
+ """
106
+ return f"{self.connection_string}:{self.table_or_query}"
107
+
108
+ def get_loader_class(self) -> builtins.type[DataSourceLoader]:
109
+ """
110
+ @methoddesc 获取 SQL 数据加载器类
111
+
112
+ 延迟导入避免循环依赖。
113
+ SQLLoader 负责使用 SQLAlchemy 建立连接并执行查询。
114
+
115
+ Returns:
116
+ SQLLoader 类
117
+ """
118
+ # 延迟导入避免循环依赖(SQLLoader 可能会引用 SQLSourceSpec)
119
+ from ..loaders import SQLLoader
120
+
121
+ return SQLLoader
122
+
123
+ def to_display_dict(self) -> dict[str, Any]:
124
+ """
125
+ @methoddesc 转换为显示用的字典(隐藏敏感信息)
126
+
127
+ 在展示连接信息时,自动将密码替换为 ***,避免敏感信息泄露。
128
+ 解析逻辑:将 connection_string 按 @ 分割,再提取用户名部分,将密码替换。
129
+
130
+ Returns:
131
+ 包含脱敏后的 connection、table_or_query 等字段的字典
132
+ """
133
+ base = super().to_display_dict() # 获取基类的通用字段
134
+
135
+ # 隐藏连接字符串中的密码
136
+ conn = self.connection_string
137
+ try:
138
+ parsed = urlparse(conn)
139
+ if parsed.password:
140
+ netloc_parts = []
141
+ if parsed.username:
142
+ netloc_parts.append(parsed.username)
143
+ netloc_parts.append(":***")
144
+ if parsed.hostname:
145
+ if netloc_parts:
146
+ netloc_parts.append("@")
147
+ netloc_parts.append(parsed.hostname)
148
+ if parsed.port:
149
+ netloc_parts.append(f":{parsed.port}")
150
+ new_netloc = "".join(netloc_parts)
151
+ conn = urlunparse(
152
+ (parsed.scheme, new_netloc, parsed.path, parsed.params, parsed.query, parsed.fragment)
153
+ )
154
+ except Exception:
155
+ pass
156
+
157
+ return {
158
+ **base,
159
+ "connection": conn, # 脱敏后的连接字符串
160
+ "table_or_query": self.table_or_query,
161
+ }
@@ -0,0 +1,36 @@
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 控制台 UTF-8 输出编码助手(CLI 与 scripts 共用)
17
+
18
+ Windows 中文终端默认编码为 GBK (cp936),直接 print 中文/符号
19
+ (✓ ✗ ⚠ 等)会抛 UnicodeEncodeError。在入口处调用本函数将
20
+ stdout/stderr 重配为 UTF-8(errors=replace 兜底)。
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import sys
26
+
27
+
28
+ def setup_utf8_console() -> None:
29
+ """将 stdout/stderr 重配置为 UTF-8 编码(仅 Windows 需要,其他平台无害)。"""
30
+ if sys.platform == "win32":
31
+ for stream in (sys.stdout, sys.stderr):
32
+ if stream and hasattr(stream, "reconfigure"):
33
+ try:
34
+ stream.reconfigure(encoding="utf-8", errors="replace")
35
+ except (AttributeError, OSError):
36
+ pass