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.
- app/__init__.py +16 -0
- app/api/__init__.py +32 -0
- app/api/dependencies.py +185 -0
- app/api/main.py +301 -0
- app/api/middleware/__init__.py +34 -0
- app/api/middleware/exception_handler.py +126 -0
- app/api/middleware/request_logging.py +97 -0
- app/api/middleware/token_auth.py +215 -0
- app/api/models/__init__.py +151 -0
- app/api/models/connection_rules.py +98 -0
- app/api/models/files.py +99 -0
- app/api/models/full_validation.py +554 -0
- app/api/models/project.py +117 -0
- app/api/models/projects.py +80 -0
- app/api/models/schema.py +121 -0
- app/api/models/v2_responses.py +46 -0
- app/api/models/validation.py +303 -0
- app/api/models/workspace.py +118 -0
- app/api/routers/__init__.py +56 -0
- app/api/routers/ai/__init__.py +36 -0
- app/api/routers/ai/chat.py +243 -0
- app/api/routers/ai/generate.py +156 -0
- app/api/routers/ai/hardware.py +199 -0
- app/api/routers/ai/jobs.py +544 -0
- app/api/routers/ai/migrate.py +471 -0
- app/api/routers/ai/models.py +352 -0
- app/api/routers/ai/ollama.py +108 -0
- app/api/routers/ai/providers.py +491 -0
- app/api/routers/ai/router.py +44 -0
- app/api/routers/ai/stream.py +281 -0
- app/api/routers/ai/utils.py +182 -0
- app/api/routers/core/__init__.py +34 -0
- app/api/routers/core/connection_rules.py +188 -0
- app/api/routers/core/data_sources.py +374 -0
- app/api/routers/core/regex.py +333 -0
- app/api/routers/core/reporting.py +263 -0
- app/api/routers/files/__init__.py +24 -0
- app/api/routers/files/ops.py +179 -0
- app/api/routers/files/transfer.py +101 -0
- app/api/routers/preview/__init__.py +35 -0
- app/api/routers/preview/content_mode.py +264 -0
- app/api/routers/preview/header_row.py +154 -0
- app/api/routers/preview/models.py +81 -0
- app/api/routers/preview/path_mode.py +440 -0
- app/api/routers/preview/router.py +30 -0
- app/api/routers/project/__init__.py +61 -0
- app/api/routers/project/base.py +111 -0
- app/api/routers/project/constraint.py +345 -0
- app/api/routers/project/full_config.py +411 -0
- app/api/routers/project/full_config_writer.py +327 -0
- app/api/routers/project/helpers.py +167 -0
- app/api/routers/project/inspection_fix.py +330 -0
- app/api/routers/project/manifest.py +605 -0
- app/api/routers/project/models.py +177 -0
- app/api/routers/project/pattern.py +268 -0
- app/api/routers/project/regex.py +358 -0
- app/api/routers/project/scanner.py +157 -0
- app/api/routers/project/schema.py +567 -0
- app/api/routers/project/schema_helpers.py +149 -0
- app/api/routers/project/settings.py +399 -0
- app/api/routers/project/template.py +325 -0
- app/api/routers/project/validation.py +227 -0
- app/api/routers/project/view.py +177 -0
- app/api/routers/project/workspaces.py +167 -0
- app/api/routers/projects/__init__.py +25 -0
- app/api/routers/projects/create.py +121 -0
- app/api/routers/projects/open.py +103 -0
- app/api/routers/projects/scan.py +100 -0
- app/api/routers/validation/__init__.py +39 -0
- app/api/routers/validation/common.py +263 -0
- app/api/routers/validation/content_mode.py +401 -0
- app/api/routers/validation/history.py +235 -0
- app/api/routers/validation/inline_mode.py +196 -0
- app/api/routers/validation/path_mode.py +314 -0
- app/api/routers/validation/router.py +51 -0
- app/api/services/full_validation_response_builder.py +412 -0
- app/api/services/io_error_messages.py +41 -0
- app/api/services/preview_service.py +248 -0
- app/cli/__init__.py +18 -0
- app/cli/__main__.py +30 -0
- app/cli/shared_services/__init__.py +42 -0
- app/cli/shared_services/config_ops.py +581 -0
- app/cli/shared_services/generation_ops.py +160 -0
- app/cli/shared_services/project_ops.py +288 -0
- app/cli/shell/__init__.py +48 -0
- app/cli/shell/commands/__init__.py +63 -0
- app/cli/shell/commands/ai/__init__.py +260 -0
- app/cli/shell/commands/ai/chat.py +331 -0
- app/cli/shell/commands/ai/delete.py +165 -0
- app/cli/shell/commands/ai/diff.py +70 -0
- app/cli/shell/commands/ai/display.py +428 -0
- app/cli/shell/commands/ai/executor.py +225 -0
- app/cli/shell/commands/ai/executor_utils.py +271 -0
- app/cli/shell/commands/ai/generate.py +285 -0
- app/cli/shell/commands/ai/interaction.py +262 -0
- app/cli/shell/commands/ai/migrate.py +281 -0
- app/cli/shell/commands/ai/resolver.py +224 -0
- app/cli/shell/commands/ai/status.py +101 -0
- app/cli/shell/commands/ai/switch.py +158 -0
- app/cli/shell/commands/ai/utils.py +52 -0
- app/cli/shell/commands/base.py +343 -0
- app/cli/shell/commands/config/__init__.py +131 -0
- app/cli/shell/commands/config/base.py +80 -0
- app/cli/shell/commands/config/check.py +246 -0
- app/cli/shell/commands/config/edit.py +148 -0
- app/cli/shell/commands/config/get.py +111 -0
- app/cli/shell/commands/config/init.py +123 -0
- app/cli/shell/commands/config/inspect.py +164 -0
- app/cli/shell/commands/config/list.py +104 -0
- app/cli/shell/commands/config/set.py +119 -0
- app/cli/shell/commands/config/show.py +150 -0
- app/cli/shell/commands/exit.py +67 -0
- app/cli/shell/commands/help.py +104 -0
- app/cli/shell/commands/infer_schema.py +156 -0
- app/cli/shell/commands/open.py +201 -0
- app/cli/shell/commands/project.py +215 -0
- app/cli/shell/commands/provider.py +619 -0
- app/cli/shell/commands/system.py +170 -0
- app/cli/shell/commands/validate.py +455 -0
- app/cli/shell/completer.py +231 -0
- app/cli/shell/config_storage.py +191 -0
- app/cli/shell/exceptions.py +90 -0
- app/cli/shell/formatter.py +371 -0
- app/cli/shell/interactive_menu.py +348 -0
- app/cli/shell/main.py +286 -0
- app/cli/shell/parser.py +266 -0
- app/cli/start.py +180 -0
- app/cli_main.py +52 -0
- app/mcp_server.py +311 -0
- app/shared/core/__init__.py +18 -0
- app/shared/core/app_version.py +59 -0
- app/shared/core/config/__init__.py +119 -0
- app/shared/core/config/server.py +193 -0
- app/shared/core/data_source/__init__.py +106 -0
- app/shared/core/data_source/loader.py +348 -0
- app/shared/core/data_source/loaders/__init__.py +213 -0
- app/shared/core/data_source/loaders/base.py +215 -0
- app/shared/core/data_source/loaders/converter.py +337 -0
- app/shared/core/data_source/loaders/csv_loader.py +252 -0
- app/shared/core/data_source/loaders/excel_loader.py +666 -0
- app/shared/core/data_source/loaders/extractor.py +268 -0
- app/shared/core/data_source/loaders/json_loader.py +354 -0
- app/shared/core/data_source/loaders/registry.py +88 -0
- app/shared/core/data_source/loaders/sql_loader.py +313 -0
- app/shared/core/data_source/loaders/strategies/__init__.py +100 -0
- app/shared/core/data_source/loaders/strategies/array_parser.py +277 -0
- app/shared/core/data_source/loaders/strategies/lines_parser.py +355 -0
- app/shared/core/data_source/loaders/strategies/object_parser.py +278 -0
- app/shared/core/data_source/schema_info.py +50 -0
- app/shared/core/data_source/specs/__init__.py +128 -0
- app/shared/core/data_source/specs/base.py +258 -0
- app/shared/core/data_source/specs/csv_source.py +137 -0
- app/shared/core/data_source/specs/excel_source.py +141 -0
- app/shared/core/data_source/specs/file_base.py +252 -0
- app/shared/core/data_source/specs/json_source.py +226 -0
- app/shared/core/data_source/specs/sql_source.py +161 -0
- app/shared/core/encoding.py +36 -0
- app/shared/core/io/__init__.py +24 -0
- app/shared/core/io/yaml.py +379 -0
- app/shared/core/manifest_schema/__init__.py +52 -0
- app/shared/core/manifest_schema/types.py +99 -0
- app/shared/core/manifest_schema/version.py +175 -0
- app/shared/core/patterns/__init__.py +24 -0
- app/shared/core/patterns/loader.py +134 -0
- app/shared/core/patterns/writer.py +244 -0
- app/shared/core/project/__init__.py +47 -0
- app/shared/core/project/constraint/builders/__init__.py +50 -0
- app/shared/core/project/constraint/builders/base.py +100 -0
- app/shared/core/project/constraint/builders/composite.py +77 -0
- app/shared/core/project/constraint/builders/conditional.py +67 -0
- app/shared/core/project/constraint/builders/foreign_key.py +53 -0
- app/shared/core/project/constraint/builders/registry.py +64 -0
- app/shared/core/project/constraint/builders/scripted.py +51 -0
- app/shared/core/project/constraint/builders/single_column.py +86 -0
- app/shared/core/project/constraint/builders/unique.py +53 -0
- app/shared/core/project/constraint/factory.py +170 -0
- app/shared/core/project/constraint/reader.py +214 -0
- app/shared/core/project/constraint/registry.py +233 -0
- app/shared/core/project/constraint/types/__init__.py +63 -0
- app/shared/core/project/constraint/types/constraint_file.py +261 -0
- app/shared/core/project/constraint/types/refs.py +460 -0
- app/shared/core/project/constraint/types.py +28 -0
- app/shared/core/project/constraint/writer.py +181 -0
- app/shared/core/project/loader/__init__.py +56 -0
- app/shared/core/project/loader/loader.py +30 -0
- app/shared/core/project/loader/loader_parts/config_inspector.py +137 -0
- app/shared/core/project/loader/loader_parts/embedded_constraints.py +224 -0
- app/shared/core/project/loader/loader_parts/file_loaders.py +58 -0
- app/shared/core/project/loader/loader_parts/inspection_ids.py +85 -0
- app/shared/core/project/loader/loader_parts/inspector_helpers.py +312 -0
- app/shared/core/project/loader/loader_parts/inspector_id_checks.py +274 -0
- app/shared/core/project/loader/loader_parts/inspector_reference_checks.py +690 -0
- app/shared/core/project/loader/loader_parts/inspector_uniqueness_checks.py +286 -0
- app/shared/core/project/loader/loader_parts/loading_error_messages.py +298 -0
- app/shared/core/project/loader/loader_parts/main.py +432 -0
- app/shared/core/project/loader/loader_parts/path_validation.py +117 -0
- app/shared/core/project/loader/loader_parts/runtime.py +125 -0
- app/shared/core/project/loader/types.py +246 -0
- app/shared/core/project/manifest/coverage.py +391 -0
- app/shared/core/project/manifest/reader.py +260 -0
- app/shared/core/project/manifest/types.py +91 -0
- app/shared/core/project/manifest/types_parts/__init__.py +60 -0
- app/shared/core/project/manifest/types_parts/constants.py +40 -0
- app/shared/core/project/manifest/types_parts/data_source.py +68 -0
- app/shared/core/project/manifest/types_parts/info.py +59 -0
- app/shared/core/project/manifest/types_parts/manifest.py +294 -0
- app/shared/core/project/manifest/types_parts/refs.py +153 -0
- app/shared/core/project/manifest/types_parts/settings.py +92 -0
- app/shared/core/project/manifest/types_parts/settings_file_processing.py +64 -0
- app/shared/core/project/manifest/types_parts/settings_script_security.py +78 -0
- app/shared/core/project/manifest/types_parts/settings_validation.py +83 -0
- app/shared/core/project/manifest/types_parts/template.py +66 -0
- app/shared/core/project/manifest/writer.py +262 -0
- app/shared/core/project/manual_data/__init__.py +27 -0
- app/shared/core/project/manual_data/types.py +75 -0
- app/shared/core/project/regex/reader.py +197 -0
- app/shared/core/project/regex/types.py +405 -0
- app/shared/core/project/regex/writer.py +123 -0
- app/shared/core/project/schema/reader.py +170 -0
- app/shared/core/project/schema/types.py +47 -0
- app/shared/core/project/schema/types_parts/__init__.py +36 -0
- app/shared/core/project/schema/types_parts/column.py +174 -0
- app/shared/core/project/schema/types_parts/column_utils.py +72 -0
- app/shared/core/project/schema/types_parts/constraint.py +165 -0
- app/shared/core/project/schema/types_parts/schema_id.py +66 -0
- app/shared/core/project/schema/types_parts/source.py +255 -0
- app/shared/core/project/schema/types_parts/source_options.py +347 -0
- app/shared/core/project/schema/types_parts/table.py +230 -0
- app/shared/core/project/schema/writer.py +139 -0
- app/shared/core/project/schema_ref_check.py +95 -0
- app/shared/core/project/template/__init__.py +27 -0
- app/shared/core/project/template/expander.py +263 -0
- app/shared/core/project/template/reader.py +120 -0
- app/shared/core/project/template/types.py +114 -0
- app/shared/core/project/transform/reader.py +76 -0
- app/shared/core/project/transform/types.py +116 -0
- app/shared/core/project/transform/writer.py +84 -0
- app/shared/core/pydantic_messages.py +59 -0
- app/shared/core/reporter/__init__.py +46 -0
- app/shared/core/reporter/reporter.py +220 -0
- app/shared/core/reporter/reporters/__init__.py +65 -0
- app/shared/core/reporter/reporters/base.py +188 -0
- app/shared/core/reporter/reporters/dingtalk_app_reporter.py +274 -0
- app/shared/core/reporter/reporters/email_reporter.py +271 -0
- app/shared/core/reporter/reporters/feishu_app_reporter.py +467 -0
- app/shared/core/reporter/reporters/local_file_reporter.py +208 -0
- app/shared/core/reporter/reporters/wecom_app_reporter.py +268 -0
- app/shared/core/utils/__init__.py +18 -0
- app/shared/core/utils/path_utils.py +60 -0
- app/shared/core/utils/regex_extract.py +113 -0
- app/shared/domain/__init__.py +114 -0
- app/shared/domain/constraints/__init__.py +76 -0
- app/shared/domain/constraints/allowed_values.py +211 -0
- app/shared/domain/constraints/base.py +141 -0
- app/shared/domain/constraints/charset.py +337 -0
- app/shared/domain/constraints/composite.py +174 -0
- app/shared/domain/constraints/condition_registry.py +130 -0
- app/shared/domain/constraints/conditional.py +629 -0
- app/shared/domain/constraints/date_logic.py +731 -0
- app/shared/domain/constraints/foreign_key.py +261 -0
- app/shared/domain/constraints/key_normalization.py +56 -0
- app/shared/domain/constraints/not_null.py +185 -0
- app/shared/domain/constraints/range.py +360 -0
- app/shared/domain/constraints/regex.py +218 -0
- app/shared/domain/constraints/scripted.py +276 -0
- app/shared/domain/constraints/unique.py +233 -0
- app/shared/domain/data_engine.py +384 -0
- app/shared/domain/data_types.py +113 -0
- app/shared/domain/data_types_parts/__init__.py +67 -0
- app/shared/domain/data_types_parts/base.py +204 -0
- app/shared/domain/data_types_parts/composite.py +250 -0
- app/shared/domain/data_types_parts/expression.py +202 -0
- app/shared/domain/data_types_parts/extracted.py +106 -0
- app/shared/domain/data_types_parts/json_types.py +234 -0
- app/shared/domain/data_types_parts/scalars.py +719 -0
- app/shared/domain/data_types_parts/sequence.py +129 -0
- app/shared/domain/dataset_schema.py +48 -0
- app/shared/domain/eval_sandbox.py +63 -0
- app/shared/domain/expression_system.py +366 -0
- app/shared/domain/regex_flags.py +53 -0
- app/shared/domain/schema/builder.py +223 -0
- app/shared/domain/schema/models.py +339 -0
- app/shared/domain/transforms/__init__.py +28 -0
- app/shared/domain/transforms/aggregate.py +141 -0
- app/shared/domain/transforms/base.py +171 -0
- app/shared/domain/transforms/cast_type.py +120 -0
- app/shared/domain/transforms/concat.py +103 -0
- app/shared/domain/transforms/conditional_assign.py +114 -0
- app/shared/domain/transforms/date_format.py +81 -0
- app/shared/domain/transforms/digits.py +77 -0
- app/shared/domain/transforms/drop_duplicates.py +94 -0
- app/shared/domain/transforms/fill_na.py +94 -0
- app/shared/domain/transforms/filter_rows.py +97 -0
- app/shared/domain/transforms/lookup.py +78 -0
- app/shared/domain/transforms/lower_case.py +70 -0
- app/shared/domain/transforms/map_value.py +90 -0
- app/shared/domain/transforms/math_expr.py +112 -0
- app/shared/domain/transforms/modulo.py +82 -0
- app/shared/domain/transforms/regex_extract.py +103 -0
- app/shared/domain/transforms/registry.py +95 -0
- app/shared/domain/transforms/replace.py +88 -0
- app/shared/domain/transforms/sort_rows.py +94 -0
- app/shared/domain/transforms/string_split.py +84 -0
- app/shared/domain/transforms/strip.py +73 -0
- app/shared/domain/transforms/substring.py +92 -0
- app/shared/domain/transforms/upper_case.py +70 -0
- app/shared/domain/transforms/weighted_sum.py +104 -0
- app/shared/domain/validation_constraints.py +69 -0
- app/shared/services/__init__.py +62 -0
- app/shared/services/ai/__init__.py +48 -0
- app/shared/services/ai/agent/__init__.py +40 -0
- app/shared/services/ai/agent/chat_tools/__init__.py +47 -0
- app/shared/services/ai/agent/chat_tools/apply_actions.py +592 -0
- app/shared/services/ai/agent/chat_tools/ask_user.py +237 -0
- app/shared/services/ai/agent/chat_tools/read_canvas.py +140 -0
- app/shared/services/ai/agent/chat_tools/read_project.py +160 -0
- app/shared/services/ai/agent/chat_tools/read_table.py +301 -0
- app/shared/services/ai/agent/chat_tools/schemas.py +105 -0
- app/shared/services/ai/agent/chat_tools/validate_table.py +131 -0
- app/shared/services/ai/agent/executor.py +554 -0
- app/shared/services/ai/agent/memory.py +256 -0
- app/shared/services/ai/agent/planner.py +282 -0
- app/shared/services/ai/agent/tool_registry.py +296 -0
- app/shared/services/ai/agent/tools/__init__.py +32 -0
- app/shared/services/ai/agent/tools/config_generate.py +133 -0
- app/shared/services/ai/agent/tools/config_refine.py +109 -0
- app/shared/services/ai/agent/tools/config_validate.py +403 -0
- app/shared/services/ai/agent/tools/merge_results.py +136 -0
- app/shared/services/ai/agent/tools/plan_chunks.py +79 -0
- app/shared/services/ai/agent/tools/script_parse.py +359 -0
- app/shared/services/ai/agent/types.py +138 -0
- app/shared/services/ai/chat_agent_runner.py +596 -0
- app/shared/services/ai/chat_orchestrator.py +725 -0
- app/shared/services/ai/failure_messages.py +65 -0
- app/shared/services/ai/job_storage.py +274 -0
- app/shared/services/ai/migrate_service.py +550 -0
- app/shared/services/ai/streaming/__init__.py +65 -0
- app/shared/services/ai/streaming/event_journal.py +197 -0
- app/shared/services/ai/streaming/orchestrator.py +262 -0
- app/shared/services/ai/streaming/pending_interaction_store.py +227 -0
- app/shared/services/ai/streaming/sse_response.py +148 -0
- app/shared/services/ai/streaming/types.py +73 -0
- app/shared/services/ai/types.py +213 -0
- app/shared/services/ai/utils.py +322 -0
- app/shared/services/diff/config_diff.py +320 -0
- app/shared/services/hardware.py +237 -0
- app/shared/services/llm/__init__.py +62 -0
- app/shared/services/llm/actions/__init__.py +42 -0
- app/shared/services/llm/actions/_canvas_validator.py +147 -0
- app/shared/services/llm/actions/_constraint_validator.py +401 -0
- app/shared/services/llm/actions/_regex_validator.py +85 -0
- app/shared/services/llm/actions/_schema_validator.py +82 -0
- app/shared/services/llm/actions/_settings_validator.py +76 -0
- app/shared/services/llm/actions/_transform_validator.py +78 -0
- app/shared/services/llm/actions/action_handlers.py +400 -0
- app/shared/services/llm/actions/action_parser.py +63 -0
- app/shared/services/llm/actions/action_processor.py +468 -0
- app/shared/services/llm/actions/action_validator.py +311 -0
- app/shared/services/llm/actions/diff_compute.py +195 -0
- app/shared/services/llm/actions/regex_handlers.py +284 -0
- app/shared/services/llm/actions/registry.py +336 -0
- app/shared/services/llm/actions/schema_handlers.py +335 -0
- app/shared/services/llm/actions/settings_handlers.py +180 -0
- app/shared/services/llm/actions/specs.py +259 -0
- app/shared/services/llm/actions/transform_handlers.py +279 -0
- app/shared/services/llm/actions/validation_types.py +147 -0
- app/shared/services/llm/cache/__init__.py +18 -0
- app/shared/services/llm/cache/response_cache.py +80 -0
- app/shared/services/llm/chat/__init__.py +35 -0
- app/shared/services/llm/chat/chat_system_prompt.py +561 -0
- app/shared/services/llm/chat/response_parser.py +296 -0
- app/shared/services/llm/config/__init__.py +45 -0
- app/shared/services/llm/config/crypto.py +135 -0
- app/shared/services/llm/config/loader.py +258 -0
- app/shared/services/llm/config/models.py +202 -0
- app/shared/services/llm/config/presets.py +128 -0
- app/shared/services/llm/config_generator.py +163 -0
- app/shared/services/llm/constraints/__init__.py +41 -0
- app/shared/services/llm/constraints/constraint_builder.py +177 -0
- app/shared/services/llm/constraints/constraint_deletion.py +84 -0
- app/shared/services/llm/constraints/constraint_id.py +174 -0
- app/shared/services/llm/constraints/frontend_instructions.py +333 -0
- app/shared/services/llm/constraints/inline_batch.py +279 -0
- app/shared/services/llm/discovery/__init__.py +35 -0
- app/shared/services/llm/discovery/scanner.py +181 -0
- app/shared/services/llm/generation/__init__.py +42 -0
- app/shared/services/llm/generation/agent_wiring.py +156 -0
- app/shared/services/llm/generation/config_builder.py +386 -0
- app/shared/services/llm/generation/errors.py +36 -0
- app/shared/services/llm/generation/existing_config.py +99 -0
- app/shared/services/llm/generation/profiler.py +279 -0
- app/shared/services/llm/generation/prompt_builder.py +199 -0
- app/shared/services/llm/generation/response_parser.py +144 -0
- app/shared/services/llm/generation/service.py +622 -0
- app/shared/services/llm/models.py +104 -0
- app/shared/services/llm/providers/__init__.py +48 -0
- app/shared/services/llm/providers/base.py +310 -0
- app/shared/services/llm/providers/cached_provider.py +90 -0
- app/shared/services/llm/providers/ollama.py +498 -0
- app/shared/services/llm/providers/openai.py +334 -0
- app/shared/services/llm/providers/registry.py +108 -0
- app/shared/services/llm/schema_resolver.py +141 -0
- app/shared/services/llm/suggestion_utils.py +219 -0
- app/shared/services/llm/validate_executor.py +163 -0
- app/shared/services/llm/yaml_io.py +406 -0
- app/shared/services/preview/__init__.py +18 -0
- app/shared/services/preview/loader.py +145 -0
- app/shared/services/preview/path_validation.py +138 -0
- app/shared/services/project_loader.py +57 -0
- app/shared/services/schema_inference.py +260 -0
- app/shared/services/schema_runtime_builder.py +120 -0
- app/shared/services/validation/__init__.py +52 -0
- app/shared/services/validation/chunked_loader.py +509 -0
- app/shared/services/validation/dag/__init__.py +35 -0
- app/shared/services/validation/dag/builder.py +141 -0
- app/shared/services/validation/dag/executor.py +225 -0
- app/shared/services/validation/dag/sorter.py +77 -0
- app/shared/services/validation/data_loader.py +231 -0
- app/shared/services/validation/engine.py +446 -0
- app/shared/services/validation/executor.py +1030 -0
- app/shared/services/validation/extractors.py +266 -0
- app/shared/services/validation/history.py +209 -0
- app/shared/services/validation/json_payload.py +136 -0
- app/shared/services/validation/loader.py +152 -0
- app/shared/services/validation/memory_monitor.py +179 -0
- app/shared/services/validation/postprocess.py +208 -0
- app/shared/services/validation/progress.py +64 -0
- app/shared/services/validation/report_export.py +222 -0
- app/shared/services/validation/resolver.py +180 -0
- app/shared/services/validation/service.py +418 -0
- app/shared/services/validation/types.py +238 -0
- app/shared/services/validation/validators/__init__.py +36 -0
- app/shared/services/validation/validators/adapter.py +182 -0
- app/shared/services/validation/validators/base.py +332 -0
- app/shared/services/validation/validators/composite.py +233 -0
- app/shared/services/validation/validators/date_logic.py +276 -0
- app/start_server.py +133 -0
- precis_cli-0.1.3.dist-info/METADATA +180 -0
- precis_cli-0.1.3.dist-info/RECORD +442 -0
- precis_cli-0.1.3.dist-info/WHEEL +5 -0
- precis_cli-0.1.3.dist-info/entry_points.txt +4 -0
- precis_cli-0.1.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,358 @@
|
|
|
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 Regex CRUD API - 正则表达式节点管理
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 提供 Regex Node(正则表达式校验/提取节点)的增删改查
|
|
21
|
+
- 支持两层查找:优先 manifest 引用,兼容扫描 patterns/、regex/、regex_nodes/ 目录
|
|
22
|
+
- 包含展示名更新能力
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- 读取时优先从 manifest.regex_nodes 定位文件,失败后扫描多个目录
|
|
26
|
+
- 写入时自动补齐 manifest 引用,默认路径为 regex/{regex_id}.regex.yaml
|
|
27
|
+
- 删除时同步清理 manifest 引用与物理文件
|
|
28
|
+
|
|
29
|
+
输入示例:
|
|
30
|
+
GET /regex/phone_number
|
|
31
|
+
PUT /regex/phone_number (body: RegexNodeFileV2)
|
|
32
|
+
|
|
33
|
+
输出示例:
|
|
34
|
+
RegexNodeFileV2 模型实例或 StandardResponse 操作结果
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
import logging
|
|
38
|
+
import os
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
|
|
41
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
from app.api.dependencies import get_project_config_path
|
|
46
|
+
from app.shared.core.io.yaml import read_yaml, write_yaml_atomic
|
|
47
|
+
from app.shared.core.project.manifest.types import ProjectManifestV2
|
|
48
|
+
|
|
49
|
+
from .base import (
|
|
50
|
+
DisplayNameUpdateRequest,
|
|
51
|
+
RegexNodeFileV2,
|
|
52
|
+
RegexNodeRefV2,
|
|
53
|
+
StandardResponse,
|
|
54
|
+
_scan_regex_node_file,
|
|
55
|
+
_v2_manifest_path,
|
|
56
|
+
)
|
|
57
|
+
from .helpers import _resolve_project_path, project_lock
|
|
58
|
+
from .manifest import get_v2_manifest
|
|
59
|
+
|
|
60
|
+
router = APIRouter(prefix="", tags=["Project-Regex"])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@router.get(
|
|
64
|
+
"/regex/{regex_id}",
|
|
65
|
+
response_model=RegexNodeFileV2,
|
|
66
|
+
summary="读取 Regex 节点",
|
|
67
|
+
responses={
|
|
68
|
+
404: {"description": "Regex 节点不存在"},
|
|
69
|
+
500: {"description": "服务器内部错误"},
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
def get_v2_regex_node(regex_id: str, config_path: str = Depends(get_project_config_path)) -> RegexNodeFileV2:
|
|
73
|
+
"""
|
|
74
|
+
读取指定 regex_id 的 regex 节点文件。
|
|
75
|
+
|
|
76
|
+
查找策略(两层查找,更健壮):
|
|
77
|
+
1. 从 manifest.regex_nodes 中查找引用(优先)
|
|
78
|
+
2. 如果未找到,扫描 patterns_dir 目录
|
|
79
|
+
|
|
80
|
+
参数:
|
|
81
|
+
regex_id: Regex 节点的唯一标识符
|
|
82
|
+
config_path: 项目配置根目录(通过 Depends 注入)
|
|
83
|
+
|
|
84
|
+
返回:
|
|
85
|
+
RegexNodeFileV2: Regex 节点文件内容
|
|
86
|
+
"""
|
|
87
|
+
manifest = get_v2_manifest(config_path)
|
|
88
|
+
|
|
89
|
+
abs_path = None
|
|
90
|
+
|
|
91
|
+
# 1. 优先从 manifest 中查找所有匹配的引用(可能存在重复 ID 的情况)
|
|
92
|
+
refs = [r for r in manifest.regex_nodes if r.id == regex_id]
|
|
93
|
+
for ref in refs:
|
|
94
|
+
path = os.path.join(config_path, ref.path)
|
|
95
|
+
if os.path.isfile(path):
|
|
96
|
+
abs_path = path
|
|
97
|
+
break
|
|
98
|
+
|
|
99
|
+
# 2. 如果 manifest 中未找到有效文件,尝试扫描目录
|
|
100
|
+
if not abs_path:
|
|
101
|
+
scan_dirs = []
|
|
102
|
+
|
|
103
|
+
p_dir = os.path.join(config_path, manifest.patterns_dir or "patterns")
|
|
104
|
+
scan_dirs.append((p_dir, "patterns"))
|
|
105
|
+
|
|
106
|
+
# B13 修复:registry 名应为 "regex",使按 regex/{id} 请求能命中。
|
|
107
|
+
# 过去误写为 "patterns" 导致目录扫描命中的 id 形如 "patterns/xxx",按 regex/{id} 请求失败。
|
|
108
|
+
scan_dirs.append((os.path.join(config_path, "regex"), "regex"))
|
|
109
|
+
scan_dirs.append((os.path.join(config_path, "regex_nodes"), "regex"))
|
|
110
|
+
|
|
111
|
+
found = False
|
|
112
|
+
for d, reg_name in scan_dirs:
|
|
113
|
+
if not os.path.isdir(d):
|
|
114
|
+
continue
|
|
115
|
+
for filename in os.listdir(d):
|
|
116
|
+
if filename.lower().endswith(".yaml") or filename.lower().endswith(".regex.yaml"):
|
|
117
|
+
rid = filename
|
|
118
|
+
if filename.lower().endswith(".regex.yaml"):
|
|
119
|
+
rid = filename[:-11]
|
|
120
|
+
elif filename.lower().endswith(".yaml"):
|
|
121
|
+
rid = filename[:-5]
|
|
122
|
+
|
|
123
|
+
match_id = rid if rid == regex_id else f"{reg_name}/{rid}"
|
|
124
|
+
|
|
125
|
+
if match_id == regex_id:
|
|
126
|
+
path = os.path.join(d, filename)
|
|
127
|
+
if os.path.isfile(path):
|
|
128
|
+
abs_path = path
|
|
129
|
+
found = True
|
|
130
|
+
break
|
|
131
|
+
if found:
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
if not abs_path:
|
|
135
|
+
raise HTTPException(status_code=404, detail=f"未找到这条正则规则,可能已被删除(ID: {regex_id})")
|
|
136
|
+
|
|
137
|
+
if not os.path.isfile(abs_path):
|
|
138
|
+
raise HTTPException(
|
|
139
|
+
status_code=404, detail="该正则规则在清单中登记的配置文件已不存在,请删除这条规则后重新创建"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
data = read_yaml(Path(abs_path))
|
|
143
|
+
|
|
144
|
+
version = data.get("version", 1)
|
|
145
|
+
if version != 2:
|
|
146
|
+
raise HTTPException(status_code=400, detail=f"不支持的 regex 文件版本: {version},仅支持 V2 格式")
|
|
147
|
+
|
|
148
|
+
return RegexNodeFileV2.model_validate(data)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@router.put(
|
|
152
|
+
"/regex/{regex_id}",
|
|
153
|
+
response_model=StandardResponse,
|
|
154
|
+
summary="写入 Regex 节点",
|
|
155
|
+
responses={
|
|
156
|
+
400: {"description": "请求参数错误"},
|
|
157
|
+
404: {"description": "Manifest 不存在"},
|
|
158
|
+
500: {"description": "服务器内部错误"},
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
def put_v2_regex_node(
|
|
162
|
+
regex_id: str,
|
|
163
|
+
regex_node: RegexNodeFileV2,
|
|
164
|
+
config_path: str = Depends(get_project_config_path),
|
|
165
|
+
) -> dict[str, str]:
|
|
166
|
+
"""
|
|
167
|
+
写入指定 regex_id 的 regex 节点文件。
|
|
168
|
+
|
|
169
|
+
业务逻辑:
|
|
170
|
+
- 验证 regex_node.id 与路径参数 regex_id 一致
|
|
171
|
+
- 如果 manifest 未引用,自动补齐引用
|
|
172
|
+
- 同时更新 manifest 文件
|
|
173
|
+
|
|
174
|
+
副作用:
|
|
175
|
+
- 创建或覆盖 regex 文件
|
|
176
|
+
- 更新 manifest 中的引用列表
|
|
177
|
+
|
|
178
|
+
数据流:
|
|
179
|
+
1. 验证 regex_node.id 与 regex_id 一致
|
|
180
|
+
2. 读取 manifest 文件
|
|
181
|
+
3. 检查 manifest 是否已有该 regex 的引用
|
|
182
|
+
4. 如果没有,添加新的引用(默认路径:regex/{regex_id}.regex.yaml)
|
|
183
|
+
5. 写入 regex 文件
|
|
184
|
+
6. 更新 manifest 文件
|
|
185
|
+
|
|
186
|
+
参数:
|
|
187
|
+
regex_id: Regex 节点的唯一标识符
|
|
188
|
+
regex_node: Regex 节点数据
|
|
189
|
+
config_path: 项目配置根目录(通过 Depends 注入)
|
|
190
|
+
|
|
191
|
+
返回:
|
|
192
|
+
StandardResponse: 操作结果消息
|
|
193
|
+
"""
|
|
194
|
+
if regex_node.id != regex_id:
|
|
195
|
+
raise HTTPException(status_code=400, detail="请求中的正则规则 ID 与地址栏不一致,请刷新页面后重试")
|
|
196
|
+
manifest_path = _v2_manifest_path(config_path)
|
|
197
|
+
if not os.path.isfile(manifest_path):
|
|
198
|
+
raise HTTPException(
|
|
199
|
+
status_code=404,
|
|
200
|
+
detail={
|
|
201
|
+
# §2.1: 结构化错误码统一(与 dependencies.py 的 manifest 缺失出口同构),
|
|
202
|
+
# 前端按 detail.code 识别项目失效并触发自愈
|
|
203
|
+
"code": "PROJECT_NOT_FOUND",
|
|
204
|
+
"message": f"项目配置文件(project.precis.yaml)不存在,请确认项目是否已被移动或删除(配置目录: {manifest_path})",
|
|
205
|
+
"path": str(
|
|
206
|
+
config_path
|
|
207
|
+
), # §2.1: 项目根目录(前端与 activeProjectPath 同口径比对,勿传 manifest 文件路径)
|
|
208
|
+
},
|
|
209
|
+
)
|
|
210
|
+
manifest = ProjectManifestV2.model_validate(read_yaml(Path(manifest_path)))
|
|
211
|
+
|
|
212
|
+
ref = next((r for r in manifest.regex_nodes if r.id == regex_id), None)
|
|
213
|
+
if not ref:
|
|
214
|
+
default_rel = f"regex/{regex_id}.regex.yaml"
|
|
215
|
+
manifest.regex_nodes.append(RegexNodeRefV2(id=regex_id, path=default_rel))
|
|
216
|
+
ref_path = default_rel
|
|
217
|
+
else:
|
|
218
|
+
ref_path = ref.path
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
abs_path = _resolve_project_path(config_path, ref_path)
|
|
222
|
+
except ValueError:
|
|
223
|
+
raise HTTPException(status_code=400, detail="非法的 Regex 文件路径")
|
|
224
|
+
with project_lock(config_path):
|
|
225
|
+
write_yaml_atomic(Path(abs_path), regex_node.model_dump(exclude_none=True))
|
|
226
|
+
write_yaml_atomic(Path(manifest_path), manifest.model_dump(exclude_none=True))
|
|
227
|
+
return {"message": f"V2 regex_node '{regex_id}' 已保存。"}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@router.delete(
|
|
231
|
+
"/regex/{regex_id}",
|
|
232
|
+
response_model=StandardResponse,
|
|
233
|
+
summary="删除 Regex 节点",
|
|
234
|
+
responses={
|
|
235
|
+
400: {"description": "请求参数错误"},
|
|
236
|
+
404: {"description": "Regex 节点或 Manifest 不存在"},
|
|
237
|
+
500: {"description": "服务器内部错误"},
|
|
238
|
+
},
|
|
239
|
+
)
|
|
240
|
+
def delete_v2_regex_node(regex_id: str, config_path: str = Depends(get_project_config_path)) -> dict[str, str]:
|
|
241
|
+
"""
|
|
242
|
+
删除指定 regex_id 的 regex 文件,并从 manifest 中移除引用。
|
|
243
|
+
|
|
244
|
+
删除策略(两层查找,更健壮):
|
|
245
|
+
1. 首先从 manifest.regex_nodes 中查找引用(优先)
|
|
246
|
+
2. 如果未找到,扫描 patterns/ 目录(兼容旧项目)
|
|
247
|
+
|
|
248
|
+
副作用:
|
|
249
|
+
- 删除对应的 .yaml 文件
|
|
250
|
+
- 从 manifest.regex_nodes 中移除引用(如果存在)
|
|
251
|
+
|
|
252
|
+
数据流:
|
|
253
|
+
1. 读取 manifest 文件
|
|
254
|
+
2. 在 manifest.regex_nodes 中查找引用
|
|
255
|
+
3. 如果未找到,扫描 patterns/ 目录
|
|
256
|
+
4. 删除找到的 regex 文件
|
|
257
|
+
5. 从 manifest 中移除引用
|
|
258
|
+
6. 保存更新后的 manifest
|
|
259
|
+
|
|
260
|
+
参数:
|
|
261
|
+
regex_id: Regex 节点的唯一标识符
|
|
262
|
+
config_path: 项目配置根目录(通过 Depends 注入)
|
|
263
|
+
|
|
264
|
+
返回:
|
|
265
|
+
StandardResponse: 操作结果消息
|
|
266
|
+
"""
|
|
267
|
+
manifest_path = _v2_manifest_path(config_path)
|
|
268
|
+
if not os.path.isfile(manifest_path):
|
|
269
|
+
raise HTTPException(
|
|
270
|
+
status_code=404,
|
|
271
|
+
detail={
|
|
272
|
+
# §2.1: 结构化错误码统一(与 dependencies.py 的 manifest 缺失出口同构),
|
|
273
|
+
# 前端按 detail.code 识别项目失效并触发自愈
|
|
274
|
+
"code": "PROJECT_NOT_FOUND",
|
|
275
|
+
"message": f"项目配置文件(project.precis.yaml)不存在,请确认项目是否已被移动或删除(配置目录: {manifest_path})",
|
|
276
|
+
"path": str(
|
|
277
|
+
config_path
|
|
278
|
+
), # §2.1: 项目根目录(前端与 activeProjectPath 同口径比对,勿传 manifest 文件路径)
|
|
279
|
+
},
|
|
280
|
+
)
|
|
281
|
+
manifest = ProjectManifestV2.model_validate(read_yaml(Path(manifest_path)))
|
|
282
|
+
|
|
283
|
+
ref = next((r for r in manifest.regex_nodes if r.id == regex_id), None)
|
|
284
|
+
if not ref:
|
|
285
|
+
ref = _scan_regex_node_file(
|
|
286
|
+
regex_id,
|
|
287
|
+
config_path,
|
|
288
|
+
manifest.patterns_dir or "patterns",
|
|
289
|
+
"regex",
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if not ref:
|
|
293
|
+
return {"message": f"未找到 regex_node '{regex_id}',无需删除。"}
|
|
294
|
+
|
|
295
|
+
try:
|
|
296
|
+
abs_path = _resolve_project_path(config_path, ref.path)
|
|
297
|
+
except ValueError:
|
|
298
|
+
raise HTTPException(status_code=400, detail="非法的 Regex 文件路径")
|
|
299
|
+
|
|
300
|
+
with project_lock(config_path):
|
|
301
|
+
try:
|
|
302
|
+
if os.path.isfile(abs_path):
|
|
303
|
+
os.remove(abs_path)
|
|
304
|
+
except Exception as e:
|
|
305
|
+
raise HTTPException(status_code=500, detail=f"删除 regex_node 文件失败: {e}")
|
|
306
|
+
|
|
307
|
+
# B-fix: 该引用原本存在于 manifest 中就写回(即使删除后列表为空)。
|
|
308
|
+
# 此前 `if manifest.regex_nodes:` 在删除最后一个引用时为假而跳过写回,
|
|
309
|
+
# manifest 会留下指向已删除文件的悬空引用。
|
|
310
|
+
remaining_regex_nodes = [r for r in manifest.regex_nodes if r.id != regex_id]
|
|
311
|
+
if len(remaining_regex_nodes) != len(manifest.regex_nodes):
|
|
312
|
+
manifest.regex_nodes = remaining_regex_nodes
|
|
313
|
+
write_yaml_atomic(Path(manifest_path), manifest.model_dump(exclude_none=True))
|
|
314
|
+
|
|
315
|
+
return {"message": f"V2 regex_node '{regex_id}' 已删除。"}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@router.post(
|
|
319
|
+
"/regex/{regex_id}/display-name",
|
|
320
|
+
response_model=StandardResponse,
|
|
321
|
+
summary="更新 Regex 节点展示名",
|
|
322
|
+
responses={
|
|
323
|
+
404: {"description": "Regex 节点不存在"},
|
|
324
|
+
500: {"description": "服务器内部错误"},
|
|
325
|
+
},
|
|
326
|
+
)
|
|
327
|
+
def update_v2_regex_node_display_name(
|
|
328
|
+
regex_id: str,
|
|
329
|
+
payload: DisplayNameUpdateRequest,
|
|
330
|
+
config_path: str = Depends(get_project_config_path),
|
|
331
|
+
) -> dict[str, str]:
|
|
332
|
+
"""
|
|
333
|
+
更新 regex 节点展示名(不改变 regex_id 与文件路径)。
|
|
334
|
+
|
|
335
|
+
设计意图:
|
|
336
|
+
- 允许用户给 Regex 节点设置友好的显示名称
|
|
337
|
+
- 不改变底层 ID 和文件路径,保持引用完整性
|
|
338
|
+
|
|
339
|
+
副作用:
|
|
340
|
+
- 会触发两次文件写入(读取-修改-保存)
|
|
341
|
+
|
|
342
|
+
数据流:
|
|
343
|
+
1. 调用 get_v2_regex_node 获取当前 regex_node
|
|
344
|
+
2. 修改 regex_node.name 为新名称
|
|
345
|
+
3. 调用 put_v2_regex_node 保存更改
|
|
346
|
+
|
|
347
|
+
参数:
|
|
348
|
+
regex_id: Regex 节点的唯一标识符
|
|
349
|
+
payload: 新的展示名称
|
|
350
|
+
config_path: 项目配置根目录(通过 Depends 注入)
|
|
351
|
+
|
|
352
|
+
返回:
|
|
353
|
+
StandardResponse: 操作结果消息
|
|
354
|
+
"""
|
|
355
|
+
regex_node = get_v2_regex_node(regex_id, config_path)
|
|
356
|
+
regex_node.name = payload.name
|
|
357
|
+
put_v2_regex_node(regex_id, regex_node, config_path)
|
|
358
|
+
return {"message": f"V2 regex_node '{regex_id}' 展示名已更新。"}
|
|
@@ -0,0 +1,157 @@
|
|
|
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
|
+
- 扫描 constraints 目录查找指定 ID 的 constraint 引用
|
|
21
|
+
- 扫描 schemas 目录查找指定 ID 的 schema 引用
|
|
22
|
+
- 扫描 patterns 和 regex 目录查找指定 ID 的 regex_node 引用
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- 纯 Python 实现,不依赖 FastAPI,便于单元测试
|
|
26
|
+
- 两层查找策略:先检查目录存在性,再遍历匹配文件名
|
|
27
|
+
- 兼容 manifest 资源列表为空或目录不存在的边界情况
|
|
28
|
+
- 文件名匹配时自动提取 ID(去掉扩展名后缀)
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
ref = _scan_schema_file("users", "/project/root")
|
|
32
|
+
|
|
33
|
+
输出示例:
|
|
34
|
+
SchemaRefV2(id="users", path="schemas/users.schema.yaml")
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
import os
|
|
38
|
+
|
|
39
|
+
from app.shared.core.project.manifest.types import (
|
|
40
|
+
ConstraintRefV2,
|
|
41
|
+
RegexNodeRefV2,
|
|
42
|
+
SchemaRefV2,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _scan_constraint_file(constraint_id: str, config_path: str) -> ConstraintRefV2 | None:
|
|
47
|
+
"""
|
|
48
|
+
扫描 constraints 目录查找指定 ID 的 constraint 引用。
|
|
49
|
+
|
|
50
|
+
扫描逻辑(两层查找,更健壮):
|
|
51
|
+
1. 检查 constraints/ 目录是否存在
|
|
52
|
+
2. 遍历目录下所有 .constraint.yaml 文件(大小写不敏感)
|
|
53
|
+
3. 从文件名中提取 constraint_id(去掉 .constraint.yaml 后缀)
|
|
54
|
+
4. 如果找到匹配则返回 ConstraintRefV2 对象
|
|
55
|
+
|
|
56
|
+
兼容的边界情况:
|
|
57
|
+
- manifest.constraints 为空的情况(部分项目可能未更新 manifest)
|
|
58
|
+
- 文件名大小写不敏感的匹配
|
|
59
|
+
- constraints 目录不存在的情况
|
|
60
|
+
|
|
61
|
+
参数:
|
|
62
|
+
constraint_id: 要查找的 constraint ID
|
|
63
|
+
config_path: 项目配置根目录
|
|
64
|
+
|
|
65
|
+
返回:
|
|
66
|
+
找到的 ConstraintRefV2 对象,未找到返回 None
|
|
67
|
+
"""
|
|
68
|
+
constraints_dir = os.path.join(config_path, "constraints")
|
|
69
|
+
if not os.path.isdir(constraints_dir):
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
for filename in os.listdir(constraints_dir):
|
|
73
|
+
if filename.lower().endswith(".constraint.yaml"):
|
|
74
|
+
scanned_id = filename[:-16]
|
|
75
|
+
if scanned_id == constraint_id:
|
|
76
|
+
return ConstraintRefV2(id=constraint_id, path=f"constraints/{filename}")
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _scan_schema_file(table_id: str, config_path: str) -> SchemaRefV2 | None:
|
|
81
|
+
"""
|
|
82
|
+
扫描 schemas 目录查找指定 ID 的 schema 引用。
|
|
83
|
+
|
|
84
|
+
扫描逻辑:
|
|
85
|
+
1. 检查 schemas/ 目录是否存在
|
|
86
|
+
2. 遍历目录下所有 .schema.yaml 文件
|
|
87
|
+
3. 从文件名中提取 table_id(去掉 .schema.yaml 后缀)
|
|
88
|
+
4. 如果找到匹配则返回 SchemaRefV2 对象
|
|
89
|
+
|
|
90
|
+
兼容的边界情况:
|
|
91
|
+
- manifest.schemas 为空的情况
|
|
92
|
+
- schemas 目录不存在的情况
|
|
93
|
+
|
|
94
|
+
参数:
|
|
95
|
+
table_id: 要查找的 schema table ID
|
|
96
|
+
config_path: 项目配置根目录
|
|
97
|
+
|
|
98
|
+
返回:
|
|
99
|
+
找到的 SchemaRefV2 对象,未找到返回 None
|
|
100
|
+
"""
|
|
101
|
+
schemas_dir = os.path.join(config_path, "schemas")
|
|
102
|
+
if not os.path.isdir(schemas_dir):
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
for filename in os.listdir(schemas_dir):
|
|
106
|
+
if filename.endswith(".schema.yaml"):
|
|
107
|
+
scanned_id = filename[:-12]
|
|
108
|
+
if scanned_id == table_id:
|
|
109
|
+
return SchemaRefV2(id=table_id, path=f"schemas/{filename}")
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _scan_regex_node_file(
|
|
114
|
+
regex_id: str,
|
|
115
|
+
config_path: str,
|
|
116
|
+
patterns_dir: str = "patterns",
|
|
117
|
+
regex_dir: str = "regex",
|
|
118
|
+
) -> RegexNodeRefV2 | None:
|
|
119
|
+
"""
|
|
120
|
+
扫描 patterns 和 regex 目录查找指定 ID 的 regex_node 引用。
|
|
121
|
+
|
|
122
|
+
扫描逻辑:
|
|
123
|
+
1. 依次检查 regex_dir 和 patterns_dir 目录
|
|
124
|
+
2. 遍历目录下的 .yaml 文件
|
|
125
|
+
3. 从文件名中提取 regex_id(去掉扩展名后缀)
|
|
126
|
+
4. 如果找到匹配则返回 RegexNodeRefV2 对象
|
|
127
|
+
|
|
128
|
+
兼容的边界情况:
|
|
129
|
+
- manifest.regex_nodes 为空的情况
|
|
130
|
+
- patterns_dir 目录不存在的情况
|
|
131
|
+
- regex_node 可能位于任一目录中
|
|
132
|
+
|
|
133
|
+
参数:
|
|
134
|
+
regex_id: 要查找的 regex node ID
|
|
135
|
+
config_path: 项目配置根目录
|
|
136
|
+
patterns_dir: patterns 目录名(默认 "patterns")
|
|
137
|
+
regex_dir: regex 目录名(默认 "regex")
|
|
138
|
+
|
|
139
|
+
返回:
|
|
140
|
+
找到的 RegexNodeRefV2 对象,未找到返回 None
|
|
141
|
+
"""
|
|
142
|
+
regex_dir_path = os.path.join(config_path, regex_dir)
|
|
143
|
+
if os.path.isdir(regex_dir_path):
|
|
144
|
+
for filename in os.listdir(regex_dir_path):
|
|
145
|
+
if filename.endswith(".regex.yaml"):
|
|
146
|
+
scanned_id = filename[:-11]
|
|
147
|
+
if scanned_id == regex_id:
|
|
148
|
+
return RegexNodeRefV2(id=regex_id, path=f"{regex_dir}/{filename}")
|
|
149
|
+
|
|
150
|
+
dir_path = os.path.join(config_path, patterns_dir)
|
|
151
|
+
if os.path.isdir(dir_path):
|
|
152
|
+
for filename in os.listdir(dir_path):
|
|
153
|
+
if filename.endswith(".yaml"):
|
|
154
|
+
scanned_id = filename[:-5]
|
|
155
|
+
if scanned_id == regex_id:
|
|
156
|
+
return RegexNodeRefV2(id=regex_id, path=f"{patterns_dir}/{filename}")
|
|
157
|
+
return None
|