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,175 @@
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
+ - 定义配置格式版本常量及支持范围检查
21
+ - 提供版本号解析、比较和版本信息查询功能
22
+
23
+ 架构设计:
24
+ - 使用模块级常量定义当前版本和支持范围
25
+ - 提供纯工具函数,无外部依赖,便于各模块调用
26
+
27
+ 输入示例:
28
+ is_supported_version(2) -> True
29
+ parse_version("2.1.0") -> (2, 1, 0)
30
+ compare_versions((2, 0, 0), (2, 1, 0)) -> -1
31
+
32
+ 输出示例:
33
+ get_version_info() -> {
34
+ "current_version": 2,
35
+ "min_supported_version": 2,
36
+ "max_supported_version": 2,
37
+ "supported_versions": [2],
38
+ }
39
+ """
40
+
41
+ from __future__ import annotations
42
+
43
+ from typing import Any
44
+
45
+ # ============================================================================
46
+ # 版本常量定义
47
+ # ============================================================================
48
+
49
+ # 当前配置格式版本号
50
+ CURRENT_VERSION = 2
51
+
52
+ # 系统最小支持的配置版本号
53
+ MIN_SUPPORTED_VERSION = 2
54
+
55
+ # 系统最大支持的配置版本号
56
+ MAX_SUPPORTED_VERSION = 2
57
+
58
+
59
+ # ============================================================================
60
+ # 版本检查与查询函数
61
+ # ============================================================================
62
+
63
+
64
+ def is_supported_version(version: int) -> bool:
65
+ """
66
+ @methoddesc 检查指定版本号是否在系统支持范围内。
67
+
68
+ 参数:
69
+ version: 要检查的版本号(整数)
70
+
71
+ 返回值:
72
+ bool: 如果版本号在最小和最大支持版本之间(含边界)则返回 True,否则返回 False
73
+
74
+ 使用示例:
75
+ >>> is_supported_version(2)
76
+ True
77
+ >>> is_supported_version(1)
78
+ False
79
+ """
80
+ return MIN_SUPPORTED_VERSION <= version <= MAX_SUPPORTED_VERSION
81
+
82
+
83
+ def get_version_info() -> dict[str, Any]:
84
+ """
85
+ @methoddesc 获取当前系统的版本信息汇总。
86
+
87
+ 返回值:
88
+ dict: 包含当前版本、最小支持版本、最大支持版本和支持版本列表的字典
89
+
90
+ 字段说明:
91
+ - current_version: 当前配置格式版本
92
+ - min_supported_version: 最小支持的版本号
93
+ - max_supported_version: 最大支持的版本号
94
+ - supported_versions: 支持的所有版本号列表(由最小到最大生成的连续整数列表)
95
+
96
+ 使用示例:
97
+ >>> get_version_info()
98
+ {'current_version': 2, 'min_supported_version': 2, 'max_supported_version': 2, 'supported_versions': [2]}
99
+ """
100
+ return {
101
+ "current_version": CURRENT_VERSION,
102
+ "min_supported_version": MIN_SUPPORTED_VERSION,
103
+ "max_supported_version": MAX_SUPPORTED_VERSION,
104
+ # 生成从最小支持版本到最大支持版本的连续版本号列表
105
+ "supported_versions": list(range(MIN_SUPPORTED_VERSION, MAX_SUPPORTED_VERSION + 1)),
106
+ }
107
+
108
+
109
+ # ============================================================================
110
+ # 版本号解析与比较函数
111
+ # ============================================================================
112
+
113
+
114
+ def parse_version(version_str: str) -> tuple[int, int, int]:
115
+ """
116
+ @methoddesc 将版本号字符串解析为 (主版本, 次版本, 修订号) 三元组。
117
+
118
+ 参数:
119
+ version_str: 版本号字符串,格式通常为 "主版本.次版本.修订号",如 "2.1.0"
120
+
121
+ 返回值:
122
+ tuple[int, int, int]: (major, minor, patch) 三元组;
123
+ 如果某部分缺失,则对应位置默认为 0
124
+
125
+ 解析逻辑:
126
+ 1. 按小数点分割字符串
127
+ 2. 依次取第 1、2、3 部分转为整数
128
+ 3. 不足三部分时,缺失部分补 0
129
+
130
+ 使用示例:
131
+ >>> parse_version("2.1.0")
132
+ (2, 1, 0)
133
+ >>> parse_version("3")
134
+ (3, 0, 0)
135
+ """
136
+ parts = version_str.split(".")
137
+ # 取主版本号,如果不存在则默认为 0
138
+ major = int(parts[0]) if len(parts) > 0 else 0
139
+ # 取次版本号,如果不存在则默认为 0
140
+ minor = int(parts[1]) if len(parts) > 1 else 0
141
+ # 取修订号,如果不存在则默认为 0
142
+ patch = int(parts[2]) if len(parts) > 2 else 0
143
+ return (major, minor, patch)
144
+
145
+
146
+ def compare_versions(v1: tuple[int, int, int], v2: tuple[int, int, int]) -> int:
147
+ """
148
+ @methoddesc 比较两个版本号三元组的大小。
149
+
150
+ 参数:
151
+ v1: 第一个版本号三元组 (major, minor, patch)
152
+ v2: 第二个版本号三元组 (major, minor, patch)
153
+
154
+ 返回值:
155
+ int: -1 表示 v1 < v2,0 表示 v1 == v2,1 表示 v1 > v2
156
+
157
+ 比较逻辑:
158
+ 直接利用 Python 元组的字典序比较特性:
159
+ 先比较主版本,若相同再比较次版本,若仍相同再比较修订号
160
+
161
+ 使用示例:
162
+ >>> compare_versions((2, 0, 0), (2, 1, 0))
163
+ -1
164
+ >>> compare_versions((3, 0, 0), (2, 9, 9))
165
+ 1
166
+ >>> compare_versions((1, 1, 1), (1, 1, 1))
167
+ 0
168
+ """
169
+ # 利用 Python 元组的字典序比较特性进行版本号大小判断
170
+ if v1 < v2:
171
+ return -1
172
+ elif v1 > v2:
173
+ return 1
174
+ else:
175
+ return 0
@@ -0,0 +1,24 @@
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
+ - 导出表达式模式加载函数(load_patterns_from_config)
20
+ """
21
+
22
+ from .loader import load_patterns_from_config
23
+
24
+ __all__ = ["load_patterns_from_config"]
@@ -0,0 +1,134 @@
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
+ - 从 patterns/ 目录加载 YAML 规则文件并编译为正则表达式
20
+ - 将 ExpressionPattern 注册到 ExpressionRegistry 供系统使用
21
+
22
+ 架构设计:
23
+ - 为避免 core 层反向依赖 domain.expression_system,
24
+ domain 相关类在函数内部延迟导入。
25
+ - 返回类型使用字符串前向引用,满足类型注解需求。
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import logging
31
+ import os
32
+ import re
33
+
34
+ logger = logging.getLogger(__name__)
35
+ from pathlib import Path
36
+ from typing import Any
37
+
38
+ from ..io.yaml import read_yaml
39
+
40
+
41
+ def load_patterns_from_config(base_dir: str) -> Any:
42
+ """
43
+ 从指定的配置目录加载所有 YAML 规则文件。
44
+
45
+ 本函数遍历指定目录下的所有 .yaml 和 .yml 文件,
46
+ 将每个文件解析为 ExpressionPattern 并注册到 ExpressionRegistry。
47
+
48
+ 加载流程:
49
+ 1. 检查目录是否存在,不存在则返回空 Registry
50
+ 2. 遍历目录下的所有 YAML 文件(按文件名排序)
51
+ 3. 读取并解析每个 YAML 文件
52
+ 4. 验证必需字段(name, regex)
53
+ 5. 创建 ExpressionPattern 对象
54
+ 6. 注册到 Registry
55
+
56
+ :param base_dir: 包含 .yaml 规则文件的目录路径,可以是相对路径或绝对路径
57
+ :return: 填充了所有已加载规则的 ExpressionRegistry 实例
58
+
59
+ YAML 文件要求:
60
+ - 必须包含 name 字段:规则名称,用于唯一标识
61
+ - 必须包含 regex 字段:正则表达式字符串
62
+ - 可选包含 description 字段:描述信息
63
+ - 可选包含 output 字段:输出配置字典
64
+
65
+ 错误处理:
66
+ - 无名称的规则文件会被跳过并输出警告
67
+ - 无正则的规则文件会被跳过并输出警告
68
+ - 加载失败的文件会输出错误信息并继续处理其他文件
69
+
70
+ 示例:
71
+ >>> registry = load_patterns_from_config("data/patterns")
72
+ >>> print(f"已加载 {len(registry.patterns)} 条规则")
73
+ """
74
+ # 延迟导入 domain 层类型,避免 core 层在模块导入时反向依赖 domain
75
+ from app.shared.domain.expression_system import ExpressionPattern, ExpressionRegistry, create_templated_parser
76
+
77
+ # 创建空的 ExpressionRegistry 实例
78
+ # 用于存储所有加载的表达式模式
79
+ registry = ExpressionRegistry()
80
+
81
+ logger.debug(f"开始从 '{base_dir}' 加载表达式规则...")
82
+
83
+ # 检查目录是否存在
84
+ if not os.path.isdir(base_dir):
85
+ # 目录不存在,输出信息并返回空 Registry
86
+ logger.debug(f"目录不存在,跳过:'{base_dir}'")
87
+ return registry
88
+
89
+ # 遍历目录下的所有文件
90
+ # 使用 sorted() 确保加载顺序一致,便于调试和复现
91
+ for filename in sorted(os.listdir(base_dir)):
92
+ # 只处理 .yaml 和 .yml 文件
93
+ if filename.endswith((".yaml", ".yml")):
94
+ # 拼接完整的文件路径
95
+ filepath = os.path.join(base_dir, filename)
96
+
97
+ try:
98
+ # 读取 YAML 文件内容
99
+ cfg = read_yaml(Path(filepath))
100
+
101
+ # 提取并验证 name 字段
102
+ name = cfg.get("name")
103
+ if not name:
104
+ logger.warning(f"跳过无名称的规则文件:{filename}")
105
+ continue
106
+
107
+ # 提取并验证 regex 字段
108
+ regex_str = cfg.get("regex")
109
+ if not regex_str:
110
+ logger.warning(f"跳过无正则的规则文件:{filename}")
111
+ continue
112
+
113
+ # 创建模板化解析器工厂函数
114
+ # output 字段可选,用于配置解析器的输出格式
115
+ parser_factory = create_templated_parser(cfg.get("output") or {})
116
+
117
+ # 编译正则表达式为 Pattern 对象
118
+ # 使用 re.compile 提高后续匹配的效率
119
+ pattern = ExpressionPattern(name=name, regex=re.compile(regex_str), parser_func=parser_factory)
120
+
121
+ # 将模式注册到 Registry
122
+ registry.register(pattern)
123
+
124
+ logger.debug(f"成功加载并注册了模板化表达式:'{name}'")
125
+
126
+ except Exception as e:
127
+ # 捕获加载过程中的所有异常
128
+ # 输出错误信息但继续处理其他文件
129
+ logger.warning(f"加载规则文件 '{filename}' 失败: {e}")
130
+
131
+ logger.debug("表达式规则加载完成。")
132
+
133
+ # 返回填充了规则的 Registry 实例
134
+ return registry
@@ -0,0 +1,244 @@
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 Pattern YAML 写入模块
18
+
19
+ 功能概述:
20
+ - 将 Pattern 数据模型序列化为 YAML 文件并写入 patterns/ 目录
21
+ - 支持自动创建目录和生成安全的文件名
22
+ - 提供 Pattern 存在性检查和唯一名称生成功能
23
+
24
+ 架构设计:
25
+ - 数据验证: 使用 Pydantic BaseModel 确保 Pattern 数据符合规范
26
+ - 安全写入: 使用 write_yaml 进行安全的 YAML 序列化
27
+ - 文件名处理: 自动清理不合法字符,解决命名冲突
28
+
29
+ PatternFile 数据模型:
30
+ name: str - Pattern 名称(必需)
31
+ regex: str - 正则表达式(必需)
32
+ description: Optional[str] - 描述信息(可选)
33
+ output: Optional[Dict[str, Any]] - 输出配置(可选)
34
+
35
+ 使用示例:
36
+ # 保存 Pattern 文件
37
+ pattern = PatternFile(name="email", regex=r"[\w\.]+@[\w\.]+", description="邮箱验证")
38
+ filepath = save_pattern_file(pattern, "patterns")
39
+
40
+ # 检查是否存在
41
+ exists = check_pattern_exists("patterns", "email")
42
+
43
+ # 生成唯一名称
44
+ unique_name = generate_unique_pattern_name("patterns", "email")
45
+ """
46
+
47
+ from __future__ import annotations
48
+
49
+ import os
50
+ from pathlib import Path
51
+ from typing import Any
52
+
53
+ from pydantic import BaseModel, Field
54
+
55
+ from ..io.yaml import write_yaml_atomic
56
+
57
+
58
+ class PatternFile(BaseModel):
59
+ """
60
+ Pattern 文件格式的数据模型。
61
+
62
+ 使用 Pydantic 进行数据验证,确保写入的 Pattern 数据符合规范。
63
+ 所有字段都有描述信息,便于文档生成和 IDE 提示。
64
+
65
+ 字段说明:
66
+ name: Pattern 的唯一标识名称,用于在 Registry 中检索
67
+ regex: 正则表达式字符串,定义匹配规则
68
+ description: 可选的描述信息,说明 Pattern 的用途
69
+ output: 可选的输出配置,定义解析器的输出格式
70
+ """
71
+
72
+ # Pattern 名称,用于唯一标识
73
+ name: str = Field(..., description="Pattern 名称")
74
+
75
+ # 正则表达式字符串
76
+ regex: str = Field(..., description="正则表达式")
77
+
78
+ # 可选的描述信息
79
+ description: str | None = Field(None, description="描述信息")
80
+
81
+ # 可选的输出配置字典
82
+ output: dict[str, Any] | None = Field(None, description="输出配置")
83
+
84
+
85
+ def save_pattern_file(pattern: PatternFile, patterns_dir: str, filename: str | None = None) -> str:
86
+ r"""
87
+ 保存 Pattern 文件到 patterns 目录。
88
+
89
+ 将 PatternFile 对象序列化为 YAML 格式并写入文件。
90
+ 支持自动生成安全的文件名,并创建必要的目录。
91
+
92
+ :param pattern: Pattern 数据模型对象
93
+ :param patterns_dir: patterns 目录路径
94
+ :param filename: 可选的文件名,默认使用 pattern.name + ".yaml"
95
+ :return: 保存的文件完整路径
96
+
97
+ 处理流程:
98
+ 1. 确定文件名(如果未提供,从 pattern.name 生成)
99
+ 2. 创建 patterns_dir 目录(如果不存在)
100
+ 3. 拼接完整的文件路径
101
+ 4. 使用 Pydantic model_dump 导出数据
102
+ 5. 调用 write_yaml 写入文件
103
+
104
+ 文件名安全处理:
105
+ - 只保留字母、数字、短横线(-)和下划线(_)
106
+ - 其他字符替换为下划线(_)
107
+ - 例如:"My Pattern!" 转换为 "My_Pattern_.yaml"
108
+
109
+ 示例:
110
+ >>> pattern = PatternFile(name="phone", regex=r"\d{3}-\d{4}")
111
+ >>> filepath = save_pattern_file(pattern, "data/patterns")
112
+ >>> print(f"已保存到: {filepath}")
113
+ """
114
+ # 如果未提供文件名,从 pattern.name 生成安全的文件名
115
+ if filename is None:
116
+ # 安全处理:将不合法字符替换为下划线
117
+ # isalnum(): 检查字符是否为字母或数字
118
+ safe_name = "".join(c if c.isalnum() or c in "-_" else "_" for c in pattern.name)
119
+ filename = f"{safe_name}.yaml"
120
+
121
+ # 创建 patterns 目录(如果不存在)
122
+ # exist_ok=True: 如果目录已存在,不抛出异常
123
+ os.makedirs(patterns_dir, exist_ok=True)
124
+
125
+ # 拼接完整的文件路径
126
+ filepath = os.path.join(patterns_dir, filename)
127
+
128
+ # 使用 Pydantic 导出数据,排除 None 值
129
+ # exclude_none=True: 不写入值为 None 的字段
130
+ data = pattern.model_dump(exclude_none=True)
131
+
132
+ # 调用 write_yaml 写入文件
133
+ write_yaml_atomic(Path(filepath), data)
134
+
135
+ # 返回完整的文件路径
136
+ return filepath
137
+
138
+
139
+ def check_pattern_exists(patterns_dir: str, pattern_name: str) -> bool:
140
+ """
141
+ 检查指定名称的 Pattern 文件是否已存在。
142
+
143
+ 用于判断是否需要创建新文件,或者是否会覆盖已有文件。
144
+
145
+ :param patterns_dir: patterns 目录路径
146
+ :param pattern_name: Pattern 名称
147
+ :return: 如果文件存在返回 True,否则返回 False
148
+
149
+ 处理流程:
150
+ 1. 检查 patterns_dir 是否为有效目录
151
+ 2. 从 pattern_name 生成安全的文件名
152
+ 3. 检查文件是否存在
153
+
154
+ 文件名生成规则与 save_pattern_file 一致:
155
+ - 只保留字母、数字、短横线和下划线
156
+ - 其他字符替换为下划线
157
+
158
+ 示例:
159
+ >>> if check_pattern_exists("patterns", "email"):
160
+ ... print("Pattern 已存在")
161
+ ... else:
162
+ ... print("可以创建新 Pattern")
163
+ """
164
+ # 检查目录是否存在
165
+ if not os.path.isdir(patterns_dir):
166
+ # 目录不存在,Pattern 文件必然不存在
167
+ return False
168
+
169
+ # 生成安全的文件名
170
+ safe_name = "".join(c if c.isalnum() or c in "-_" else "_" for c in pattern_name)
171
+
172
+ # 拼接文件路径
173
+ filepath = os.path.join(patterns_dir, f"{safe_name}.yaml")
174
+
175
+ # 检查文件是否存在并返回结果
176
+ return os.path.isfile(filepath)
177
+
178
+
179
+ def generate_unique_pattern_name(patterns_dir: str, base_name: str) -> str:
180
+ """
181
+ 生成唯一的 Pattern 名称,处理命名冲突。
182
+
183
+ 如果 base_name 已存在,会自动在名称后添加数字后缀,
184
+ 直到找到不冲突的名称。例如:email, email_1, email_2, ...
185
+
186
+ :param patterns_dir: patterns 目录路径
187
+ :param base_name: 基础名称
188
+ :return: 唯一的名称(如果基础名称可用则返回原名称,否则返回带数字后缀的名称)
189
+
190
+ 处理流程:
191
+ 1. 首先检查 base_name 是否可用
192
+ 2. 如果不可用,从 1 开始递增尝试
193
+ 3. 找到第一个不存在的名称后返回
194
+
195
+ 示例:
196
+ >>> generate_unique_pattern_name("patterns", "email")
197
+ 'email' # 如果 email 不存在
198
+
199
+ >>> generate_unique_pattern_name("patterns", "email")
200
+ 'email_3' # 如果 email, email_1, email_2 都已存在
201
+ """
202
+ # 首先检查基础名称是否可用
203
+ if not check_pattern_exists(patterns_dir, base_name):
204
+ return base_name
205
+
206
+ # 基础名称已存在,从 1 开始尝试
207
+ counter = 1
208
+
209
+ # 循环尝试,直到找到不存在的名称
210
+ while True:
211
+ # 生成带数字后缀的名称
212
+ new_name = f"{base_name}_{counter}"
213
+
214
+ # 检查新名称是否可用
215
+ if not check_pattern_exists(patterns_dir, new_name):
216
+ return new_name
217
+
218
+ # 继续尝试下一个数字
219
+ counter += 1
220
+
221
+
222
+ def get_pattern_filepath(patterns_dir: str, pattern_name: str) -> str:
223
+ """根据 pattern 名称推导其文件路径(与 save 的命名规则一致)。
224
+
225
+ :param patterns_dir: patterns 目录路径
226
+ :param pattern_name: Pattern 名称
227
+ :return: 完整文件路径
228
+ """
229
+ safe_name = "".join(c if c.isalnum() or c in "-_" else "_" for c in pattern_name)
230
+ return os.path.join(patterns_dir, f"{safe_name}.yaml")
231
+
232
+
233
+ def delete_pattern_file(patterns_dir: str, pattern_name: str) -> bool:
234
+ """删除指定 Pattern 文件。
235
+
236
+ :param patterns_dir: patterns 目录路径
237
+ :param pattern_name: Pattern 名称
238
+ :return: 删除成功返回 True,文件不存在返回 False
239
+ """
240
+ filepath = get_pattern_filepath(patterns_dir, pattern_name)
241
+ if not os.path.isfile(filepath):
242
+ return False
243
+ os.remove(filepath)
244
+ return True
@@ -0,0 +1,47 @@
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 项目配置模块(V2)统一入口
18
+
19
+ 功能概述:
20
+ - 聚合导出项目加载的核心类型与入口函数
21
+ - 对外提供统一的 load_project 加载接口
22
+ - 作为 app.shared.core.project 包的门面(Facade),隐藏内部子模块细节
23
+
24
+ 架构设计:
25
+ - 门面模式: 将 loader、types 等子模块的实现细节封装在内部,
26
+ 调用方只需导入本模块即可获得全部所需功能,降低耦合度。
27
+ - 类型重导出: LoadedProject、LoadingError 等核心类型从底层子模块
28
+ 重新导出,避免调用方直接依赖内部文件路径,便于后续重构。
29
+
30
+ 导入路径简化示例:
31
+ >>> from app.shared.core.project import load_project, LoadedProject
32
+ >>> project = load_project(Path("project.precis.yaml"))
33
+ >>> print(project.schemas)
34
+
35
+ 注意事项:
36
+ - 若需新增对外暴露的类型或函数,应在本模块执行重导出,
37
+ 并在 __all__ 中补充对应名称以保持显式控制。
38
+ - 禁止在本模块编写具体业务逻辑,所有实现应下沉到子模块中。
39
+ """
40
+
41
+ # 从 loader 子模块导入核心类型和入口函数
42
+ # 采用相对导入,保持包内引用的一致性
43
+ from .loader import LoadedProject, load_project
44
+
45
+ # __all__ 控制 `from app.shared.core.project import *` 时导出的名称列表
46
+ # 显式列出公开 API,避免意外暴露内部辅助函数或子模块
47
+ __all__ = ["load_project", "LoadedProject"]
@@ -0,0 +1,50 @@
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
+ 通过 side-effect import 触发各 builder 的 @register_builder 注册。
19
+ 新增约束类型时,新建 builder 文件并在本文件 import 即可自动注册。
20
+
21
+ 注册清单:
22
+ - single_column: NotNull / AllowedValues / DateLogic / Range / Charset
23
+ - unique: Unique(多列 list 语义)
24
+ - foreign_key: ForeignKey(双向引用)
25
+ - conditional: Conditional(IF 条件列表)
26
+ - scripted: Scripted(可选列)
27
+ - composite: Composite(递归子约束)
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ # side-effect import:触发各模块顶层的 @register_builder 调用
33
+ from . import ( # noqa: F401
34
+ composite,
35
+ conditional,
36
+ foreign_key,
37
+ scripted,
38
+ single_column,
39
+ unique,
40
+ )
41
+ from .base import BuilderInput, BuilderResult
42
+ from .registry import CONSTRAINT_BUILDERS, build_kwargs, register_builder
43
+
44
+ __all__ = [
45
+ "BuilderInput",
46
+ "BuilderResult",
47
+ "CONSTRAINT_BUILDERS",
48
+ "build_kwargs",
49
+ "register_builder",
50
+ ]