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,347 @@
|
|
|
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
|
+
- 实现 FormatOptions 联合类型
|
|
22
|
+
- 支持 JSON、CSV、Excel 等格式的特定配置
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- JSONOptions: JSON 格式配置(format, json_path, sep, dtype)
|
|
26
|
+
- CSVOptions: CSV 格式配置(delimiter, encoding, quote_char)
|
|
27
|
+
- ExcelOptions: Excel 格式配置(暂无特有选项)
|
|
28
|
+
- FormatOptions: 联合类型,通过 Union 实现多态
|
|
29
|
+
|
|
30
|
+
输入示例 (schema.yaml):
|
|
31
|
+
# JSON 格式选项
|
|
32
|
+
source:
|
|
33
|
+
path: data/users.json
|
|
34
|
+
options:
|
|
35
|
+
format: auto
|
|
36
|
+
json_path: "$.data.items"
|
|
37
|
+
sep: "."
|
|
38
|
+
|
|
39
|
+
# CSV 格式选项
|
|
40
|
+
source:
|
|
41
|
+
path: data/users.csv
|
|
42
|
+
options:
|
|
43
|
+
delimiter: ","
|
|
44
|
+
encoding: utf-8
|
|
45
|
+
skip_rows: 0
|
|
46
|
+
|
|
47
|
+
# Excel 格式选项
|
|
48
|
+
source:
|
|
49
|
+
path: data/users.xlsx
|
|
50
|
+
options:
|
|
51
|
+
engine: openpyxl
|
|
52
|
+
dtype_inference: true
|
|
53
|
+
|
|
54
|
+
输出示例:
|
|
55
|
+
# JSON 格式选项
|
|
56
|
+
JSONOptions(format="auto", json_path="$.data.items", sep=".")
|
|
57
|
+
|
|
58
|
+
# CSV 格式选项
|
|
59
|
+
CSVOptions(delimiter=",", encoding="utf-8", skip_rows=0)
|
|
60
|
+
|
|
61
|
+
# Excel 格式选项
|
|
62
|
+
ExcelOptions(engine="openpyxl", dtype_inference=True)
|
|
63
|
+
|
|
64
|
+
# 联合类型使用
|
|
65
|
+
SourceSpec(
|
|
66
|
+
mode="relative_file",
|
|
67
|
+
path="data/users.json",
|
|
68
|
+
options=JSONOptions(format="array")
|
|
69
|
+
)
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
from __future__ import annotations
|
|
73
|
+
|
|
74
|
+
from typing import Any, Literal
|
|
75
|
+
|
|
76
|
+
from pydantic import BaseModel, Field
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class JSONOptions(BaseModel):
|
|
80
|
+
"""@classdesc JSON 格式选项
|
|
81
|
+
|
|
82
|
+
包含 JSON 数据源的特定配置。
|
|
83
|
+
|
|
84
|
+
字段说明:
|
|
85
|
+
- format: JSON 格式类型
|
|
86
|
+
- auto: 自动检测
|
|
87
|
+
- array: 对象数组格式
|
|
88
|
+
- lines: JSON Lines 格式
|
|
89
|
+
- object: 嵌套对象格式
|
|
90
|
+
- json_path: JSONPath 提取路径
|
|
91
|
+
- record_path: 要展平的路径
|
|
92
|
+
- meta_prefix: 元数据字段前缀
|
|
93
|
+
- sep: 嵌套对象展平时的分隔符
|
|
94
|
+
- dtype: 列类型指定
|
|
95
|
+
|
|
96
|
+
输入示例:
|
|
97
|
+
options:
|
|
98
|
+
format: object
|
|
99
|
+
json_path: "$.data.items"
|
|
100
|
+
sep: "."
|
|
101
|
+
|
|
102
|
+
输出示例:
|
|
103
|
+
JSONOptions(
|
|
104
|
+
format="object",
|
|
105
|
+
json_path="$.data.items",
|
|
106
|
+
sep="."
|
|
107
|
+
)
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
format: Literal["array", "lines", "object"] = Field(
|
|
111
|
+
..., description="JSON 格式类型(必填):array-顶层数组, lines-JSON Lines, object-嵌套对象(需 json_path)"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
json_path: str | None = Field(None, description="JSONPath 提取路径,如 '$.data.items'(用于从嵌套对象提取数组)")
|
|
115
|
+
|
|
116
|
+
record_path: str | None = Field(None, description="要展平的路径,如 'items'(用于数组字段)")
|
|
117
|
+
|
|
118
|
+
meta_prefix: str = Field("meta.", description="元数据字段前缀")
|
|
119
|
+
|
|
120
|
+
sep: str = Field(".", description="嵌套对象展平时的分隔符")
|
|
121
|
+
|
|
122
|
+
dtype: dict[str, str] | None = Field(None, description="列类型指定,如 {'id': 'str', 'count': 'int'}")
|
|
123
|
+
|
|
124
|
+
flatten: bool = Field(False, description="是否自动扁平化嵌套结构")
|
|
125
|
+
|
|
126
|
+
def to_loader_config(self) -> dict[str, Any]:
|
|
127
|
+
"""@methoddesc 转换为加载器配置字典
|
|
128
|
+
|
|
129
|
+
将 JSONOptions 对象转换为数据加载器可识别的配置字典。
|
|
130
|
+
加载器使用这些参数来正确解析 JSON 文件。
|
|
131
|
+
|
|
132
|
+
返回:
|
|
133
|
+
包含 type、format、json_path 等键的字典
|
|
134
|
+
|
|
135
|
+
示例:
|
|
136
|
+
>>> opts = JSONOptions(format="array", json_path="$.data")
|
|
137
|
+
>>> opts.to_loader_config()
|
|
138
|
+
{'type': 'json', 'format': 'array', 'json_path': '$.data', ...}
|
|
139
|
+
"""
|
|
140
|
+
return {
|
|
141
|
+
"type": "json",
|
|
142
|
+
"format": self.format,
|
|
143
|
+
"json_path": self.json_path,
|
|
144
|
+
"record_path": self.record_path,
|
|
145
|
+
"meta_prefix": self.meta_prefix,
|
|
146
|
+
"sep": self.sep,
|
|
147
|
+
"dtype": self.dtype,
|
|
148
|
+
"flatten": self.flatten,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class CSVOptions(BaseModel):
|
|
153
|
+
"""@classdesc CSV 格式选项
|
|
154
|
+
|
|
155
|
+
包含 CSV 数据源的特定配置。
|
|
156
|
+
|
|
157
|
+
字段说明:
|
|
158
|
+
- delimiter: 字段分隔符(默认逗号)
|
|
159
|
+
- quotechar: 引号字符(默认双引号)
|
|
160
|
+
- escapechar: 转义字符
|
|
161
|
+
- encoding: 文件编码(默认 utf-8)
|
|
162
|
+
- skip_rows: 跳过的行数
|
|
163
|
+
- on_bad_lines: 遇到坏行的处理方式
|
|
164
|
+
|
|
165
|
+
输入示例:
|
|
166
|
+
options:
|
|
167
|
+
delimiter: ","
|
|
168
|
+
encoding: utf-8
|
|
169
|
+
skip_rows: 0
|
|
170
|
+
|
|
171
|
+
输出示例:
|
|
172
|
+
CSVOptions(
|
|
173
|
+
delimiter=",",
|
|
174
|
+
encoding="utf-8",
|
|
175
|
+
skip_rows=0
|
|
176
|
+
)
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
delimiter: str = Field(",", description="字段分隔符")
|
|
180
|
+
|
|
181
|
+
quotechar: str = Field('"', description="引号字符")
|
|
182
|
+
|
|
183
|
+
escapechar: str | None = Field(None, description="转义字符")
|
|
184
|
+
|
|
185
|
+
encoding: str = Field("utf-8", description="文件编码")
|
|
186
|
+
|
|
187
|
+
skip_rows: int = Field(0, ge=0, description="跳过的行数")
|
|
188
|
+
|
|
189
|
+
# 默认 error(校验工具红线:坏行静默跳过=漏报;warn/skip 为显式 opt-in,
|
|
190
|
+
# 与 CSVSourceSpec.on_bad_lines 默认值保持一致,标准/分块两路径同口径)
|
|
191
|
+
on_bad_lines: Literal["error", "warn", "skip"] = Field(
|
|
192
|
+
"error", description="遇到坏行的处理方式:error-报错(默认,fail-closed), warn-警告并跳过, skip-静默跳过"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def to_loader_config(self) -> dict[str, Any]:
|
|
196
|
+
"""@methoddesc 转换为加载器配置字典
|
|
197
|
+
|
|
198
|
+
将 CSVOptions 对象转换为数据加载器可识别的配置字典。
|
|
199
|
+
加载器使用这些参数来正确解析 CSV 文件。
|
|
200
|
+
|
|
201
|
+
返回:
|
|
202
|
+
包含 type、delimiter、encoding 等键的字典
|
|
203
|
+
|
|
204
|
+
示例:
|
|
205
|
+
>>> opts = CSVOptions(delimiter=";", encoding="gbk")
|
|
206
|
+
>>> opts.to_loader_config()
|
|
207
|
+
{'type': 'csv', 'delimiter': ';', 'encoding': 'gbk', ...}
|
|
208
|
+
"""
|
|
209
|
+
return {
|
|
210
|
+
"type": "csv",
|
|
211
|
+
"delimiter": self.delimiter,
|
|
212
|
+
"quotechar": self.quotechar,
|
|
213
|
+
"escapechar": self.escapechar,
|
|
214
|
+
"encoding": self.encoding,
|
|
215
|
+
"skip_rows": self.skip_rows,
|
|
216
|
+
"on_bad_lines": self.on_bad_lines,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class ExcelOptions(BaseModel):
|
|
221
|
+
"""@classdesc Excel 格式选项
|
|
222
|
+
|
|
223
|
+
包含 Excel 数据源的特定配置。
|
|
224
|
+
|
|
225
|
+
字段说明:
|
|
226
|
+
- engine: 读取引擎(openpyxl 或 xlrd)
|
|
227
|
+
- dtype_inference: 是否自动推断数据类型
|
|
228
|
+
|
|
229
|
+
输入示例:
|
|
230
|
+
options:
|
|
231
|
+
engine: openpyxl
|
|
232
|
+
dtype_inference: true
|
|
233
|
+
|
|
234
|
+
输出示例:
|
|
235
|
+
ExcelOptions(
|
|
236
|
+
engine="openpyxl",
|
|
237
|
+
dtype_inference=True
|
|
238
|
+
)
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
engine: Literal["openpyxl", "xlrd"] = Field("openpyxl", description="读取引擎")
|
|
242
|
+
|
|
243
|
+
dtype_inference: bool = Field(True, description="是否自动推断数据类型")
|
|
244
|
+
|
|
245
|
+
def to_loader_config(self) -> dict[str, Any]:
|
|
246
|
+
"""@methoddesc 转换为加载器配置字典
|
|
247
|
+
|
|
248
|
+
将 ExcelOptions 对象转换为数据加载器可识别的配置字典。
|
|
249
|
+
加载器使用这些参数来正确解析 Excel 文件。
|
|
250
|
+
|
|
251
|
+
返回:
|
|
252
|
+
包含 type、engine、dtype_inference 等键的字典
|
|
253
|
+
|
|
254
|
+
示例:
|
|
255
|
+
>>> opts = ExcelOptions(engine="openpyxl")
|
|
256
|
+
>>> opts.to_loader_config()
|
|
257
|
+
{'type': 'excel', 'engine': 'openpyxl', 'dtype_inference': True}
|
|
258
|
+
"""
|
|
259
|
+
return {
|
|
260
|
+
"type": "excel",
|
|
261
|
+
"engine": self.engine,
|
|
262
|
+
"dtype_inference": self.dtype_inference,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# 格式选项联合类型
|
|
267
|
+
# 使用联合类型实现多态,支持不同格式的特定配置
|
|
268
|
+
# 这意味着一个变量可以是 JSONOptions、CSVOptions 或 ExcelOptions 中的任意一种
|
|
269
|
+
FormatOptions = JSONOptions | CSVOptions | ExcelOptions
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def create_format_options(file_ext: str, options_dict: dict | None = None) -> FormatOptions | None:
|
|
273
|
+
"""@methoddesc 根据文件扩展名创建对应的格式选项对象
|
|
274
|
+
|
|
275
|
+
本函数是格式选项的工厂方法,根据文件扩展名自动选择正确的选项类,
|
|
276
|
+
并将配置字典转换为对应的 Pydantic 模型对象。
|
|
277
|
+
|
|
278
|
+
参数说明:
|
|
279
|
+
:param file_ext: 文件扩展名,支持以下格式:
|
|
280
|
+
- ".json", ".jsonl", ".ndjson" -> JSONOptions
|
|
281
|
+
- ".csv" -> CSVOptions
|
|
282
|
+
- ".xlsx", ".xls" -> ExcelOptions
|
|
283
|
+
:param options_dict: 选项配置字典(来自 schema.yaml 的 options 字段)
|
|
284
|
+
:return: 对应格式的选项对象,如果扩展名不支持或 options_dict 为空则返回 None
|
|
285
|
+
|
|
286
|
+
示例:
|
|
287
|
+
>>> # 创建 JSON 选项
|
|
288
|
+
>>> opts = create_format_options('.json', {'format': 'array', 'json_path': '$.data'})
|
|
289
|
+
>>> type(opts).__name__
|
|
290
|
+
'JSONOptions'
|
|
291
|
+
|
|
292
|
+
>>> # 创建 CSV 选项
|
|
293
|
+
>>> opts = create_format_options('.csv', {'delimiter': ';', 'encoding': 'gbk'})
|
|
294
|
+
>>> type(opts).__name__
|
|
295
|
+
'CSVOptions'
|
|
296
|
+
|
|
297
|
+
>>> # 无配置时返回 None
|
|
298
|
+
>>> opts = create_format_options('.json', None)
|
|
299
|
+
>>> opts is None
|
|
300
|
+
True
|
|
301
|
+
"""
|
|
302
|
+
# 如果未提供配置字典,直接返回 None(表示使用默认配置)
|
|
303
|
+
if not options_dict:
|
|
304
|
+
return None
|
|
305
|
+
|
|
306
|
+
# 将扩展名统一转为小写,保证大小写不敏感匹配
|
|
307
|
+
ext = file_ext.lower()
|
|
308
|
+
|
|
309
|
+
# 根据扩展名创建对应的格式选项对象
|
|
310
|
+
# JSON 格式(包括标准 JSON、JSON Lines、Newline Delimited JSON)
|
|
311
|
+
if ext in (".json", ".jsonl", ".ndjson"):
|
|
312
|
+
return JSONOptions(**options_dict)
|
|
313
|
+
# CSV 格式
|
|
314
|
+
elif ext == ".csv":
|
|
315
|
+
return CSVOptions(**options_dict)
|
|
316
|
+
# Excel 格式(包括 .xlsx 和 .xls)
|
|
317
|
+
elif ext in (".xlsx", ".xls"):
|
|
318
|
+
return ExcelOptions(**options_dict)
|
|
319
|
+
|
|
320
|
+
# 不支持的扩展名,返回 None
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def get_options_type(options: FormatOptions | None) -> str:
|
|
325
|
+
"""@methoddesc 获取格式选项的类型名称
|
|
326
|
+
|
|
327
|
+
用于调试和日志记录,快速判断一个 FormatOptions 对象具体是哪种类型。
|
|
328
|
+
|
|
329
|
+
参数说明:
|
|
330
|
+
:param options: 格式选项对象(JSONOptions / CSVOptions / ExcelOptions)
|
|
331
|
+
:return: 类型名称字符串
|
|
332
|
+
- "JSONOptions" - JSON 格式选项
|
|
333
|
+
- "CSVOptions" - CSV 格式选项
|
|
334
|
+
- "ExcelOptions" - Excel 格式选项
|
|
335
|
+
- "None" - 空值
|
|
336
|
+
|
|
337
|
+
示例:
|
|
338
|
+
>>> get_options_type(JSONOptions(format="array"))
|
|
339
|
+
'JSONOptions'
|
|
340
|
+
>>> get_options_type(None)
|
|
341
|
+
'None'
|
|
342
|
+
"""
|
|
343
|
+
# 如果 options 为 None,直接返回 "None"
|
|
344
|
+
if options is None:
|
|
345
|
+
return "None"
|
|
346
|
+
# 使用 Python 内置的 type() 函数获取对象的类名
|
|
347
|
+
return type(options).__name__
|
|
@@ -0,0 +1,230 @@
|
|
|
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
|
+
- 定义 Schema 文件的主要结构 (TableSchemaFile)
|
|
21
|
+
- 包含表 ID、名称、列定义、约束、数据源等
|
|
22
|
+
|
|
23
|
+
架构设计:
|
|
24
|
+
- 主模型: TableSchemaFile 是 schema.yaml 解析后的根对象
|
|
25
|
+
- 验证: 自动检查列 ID/name 唯一性、约束 ID 唯一性
|
|
26
|
+
- 兼容性: source.sheet 和 sheet 字段互斥
|
|
27
|
+
|
|
28
|
+
输入示例 (schema.yaml):
|
|
29
|
+
version: 2
|
|
30
|
+
id: sc_xxxencodedid
|
|
31
|
+
name: 用户表
|
|
32
|
+
source:
|
|
33
|
+
path: data/users.xlsx
|
|
34
|
+
sheet: Sheet1
|
|
35
|
+
header_row: 0
|
|
36
|
+
columns:
|
|
37
|
+
- id: col_1
|
|
38
|
+
name: id
|
|
39
|
+
type: integer
|
|
40
|
+
primary_key: true
|
|
41
|
+
- id: col_2
|
|
42
|
+
name: username
|
|
43
|
+
type: string
|
|
44
|
+
nullable: false
|
|
45
|
+
constraints:
|
|
46
|
+
- id: not_null_username
|
|
47
|
+
type: NotNull
|
|
48
|
+
column: username
|
|
49
|
+
|
|
50
|
+
输出示例:
|
|
51
|
+
TableSchemaFile(
|
|
52
|
+
version=2,
|
|
53
|
+
id="sc_xxxencodedid",
|
|
54
|
+
name="用户表",
|
|
55
|
+
source=SourceSpec(path="data/users.xlsx", sheet="Sheet1", header_row=0),
|
|
56
|
+
columns=[
|
|
57
|
+
ColumnSpec(id="col_1", name="id", type="integer", primary_key=True),
|
|
58
|
+
ColumnSpec(id="col_2", name="username", type="string", nullable=False)
|
|
59
|
+
],
|
|
60
|
+
constraints=[
|
|
61
|
+
ConstraintItem(id="not_null_username", type="NotNull", column="username")
|
|
62
|
+
],
|
|
63
|
+
script_checks=[]
|
|
64
|
+
)
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
from __future__ import annotations
|
|
68
|
+
|
|
69
|
+
from typing import Any
|
|
70
|
+
|
|
71
|
+
from pydantic import BaseModel, Field, model_validator
|
|
72
|
+
|
|
73
|
+
from app.shared.core.project.schema.types_parts.column import ColumnSpec
|
|
74
|
+
from app.shared.core.project.schema.types_parts.constraint import ConstraintItem
|
|
75
|
+
from app.shared.core.project.schema.types_parts.source import SourceSpec
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TableSchemaFile(BaseModel):
|
|
79
|
+
"""@classdesc 表结构文件模型
|
|
80
|
+
|
|
81
|
+
这是一个 schema.yaml 文件解析后的根对象。
|
|
82
|
+
|
|
83
|
+
字段说明:
|
|
84
|
+
- version: 配置文件版本,固定为 2
|
|
85
|
+
- id: 表的唯一标识符 (系统生成或手动指定)
|
|
86
|
+
- name: 表的展示名称 (用于 UI 显示和校验错误提示)
|
|
87
|
+
- source: 数据源描述 (可选,不提供时自动发现)
|
|
88
|
+
- sheet: Excel 工作表名 (与 source.sheet 互斥)
|
|
89
|
+
- columns: 列定义列表
|
|
90
|
+
- constraints: 内嵌约束列表 (可直接在 schema 中定义)
|
|
91
|
+
- script_checks: 脚本化检查列表
|
|
92
|
+
|
|
93
|
+
数据校验:
|
|
94
|
+
- 列 ID 唯一性: 所有 columns.id 必须唯一
|
|
95
|
+
- 列名唯一性: 所有 columns.name 必须唯一
|
|
96
|
+
- 约束 ID 唯一性: 所有 constraints.id 必须唯一
|
|
97
|
+
- sheet 一致性: source.sheet 和 sheet 字段不能同时指定且不同
|
|
98
|
+
|
|
99
|
+
示例:
|
|
100
|
+
# 最小配置
|
|
101
|
+
id: users
|
|
102
|
+
name: 用户表
|
|
103
|
+
columns:
|
|
104
|
+
- id: col_1
|
|
105
|
+
name: id
|
|
106
|
+
type: integer
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
version: int = Field(2, description="配置版本号(固定为 2)")
|
|
110
|
+
id: str = Field(..., description="表 ID(稳定标识)")
|
|
111
|
+
name: str = Field(..., description="表名(展示/校验/数据文件发现使用)")
|
|
112
|
+
source: SourceSpec | None = Field(None, description="数据源描述(为空时根据 name 自动发现)")
|
|
113
|
+
sheet: str | None = Field(None, description="Excel 工作表名称")
|
|
114
|
+
columns: list[ColumnSpec] = Field(default_factory=list, description="列定义列表")
|
|
115
|
+
constraints: list[ConstraintItem] = Field(
|
|
116
|
+
default_factory=list,
|
|
117
|
+
description="内嵌约束列表(可直接在 schema.yaml 中定义,无需独立文件)",
|
|
118
|
+
)
|
|
119
|
+
script_checks: list[dict[str, Any]] = Field(default_factory=list, description="脚本化检查")
|
|
120
|
+
|
|
121
|
+
@model_validator(mode="after")
|
|
122
|
+
def _validate_source_sheet_compatibility(self) -> TableSchemaFile:
|
|
123
|
+
"""@methoddesc 验证 source.sheet 和 sheet 字段的一致性
|
|
124
|
+
|
|
125
|
+
业务规则:
|
|
126
|
+
- source.sheet 和 sheet 字段表示同一个含义(Excel 工作表名)
|
|
127
|
+
- 如果两者同时指定且值不同,会导致歧义,不知道以哪个为准
|
|
128
|
+
- 校验逻辑:如果同时存在且不一致,抛出 ValueError
|
|
129
|
+
|
|
130
|
+
为什么需要这个校验?
|
|
131
|
+
- 防止用户在两个地方写了不同的工作表名,导致数据读取错误
|
|
132
|
+
- 建议:优先使用 source.sheet,sheet 字段为兼容保留
|
|
133
|
+
|
|
134
|
+
示例:
|
|
135
|
+
# 合法:只在一个地方声明
|
|
136
|
+
source:
|
|
137
|
+
sheet: Sheet1
|
|
138
|
+
|
|
139
|
+
# 合法:两处声明一致
|
|
140
|
+
source:
|
|
141
|
+
sheet: Sheet1
|
|
142
|
+
sheet: Sheet1
|
|
143
|
+
|
|
144
|
+
# 非法:两处声明不一致(会抛出 ValueError)
|
|
145
|
+
source:
|
|
146
|
+
sheet: Sheet1
|
|
147
|
+
sheet: Sheet2
|
|
148
|
+
"""
|
|
149
|
+
if self.source and self.sheet:
|
|
150
|
+
source_sheet = self.source.sheet
|
|
151
|
+
if source_sheet and source_sheet != self.sheet:
|
|
152
|
+
raise ValueError(
|
|
153
|
+
f"source.sheet ('{source_sheet}') 与 sheet ('{self.sheet}') 不一致。请只在一个位置声明 sheet 名称。"
|
|
154
|
+
)
|
|
155
|
+
return self
|
|
156
|
+
|
|
157
|
+
@model_validator(mode="after")
|
|
158
|
+
def _validate_columns_unique(self) -> TableSchemaFile:
|
|
159
|
+
"""@methoddesc 验证列 ID 和列名的唯一性
|
|
160
|
+
|
|
161
|
+
业务规则:
|
|
162
|
+
- 同一表内的所有列 ID 必须唯一(用于内部引用)
|
|
163
|
+
- 同一表内的所有列名必须唯一(用于数据访问和展示)
|
|
164
|
+
|
|
165
|
+
校验逻辑:
|
|
166
|
+
1. 提取所有列的 id 列表
|
|
167
|
+
2. 使用 set 去重后比较长度,如果不相等说明有重复
|
|
168
|
+
3. 同样方式检查 name 列表
|
|
169
|
+
|
|
170
|
+
为什么需要这个校验?
|
|
171
|
+
- 列 ID 用于约束和正则节点中引用列,重复会导致引用歧义
|
|
172
|
+
- 列名用于数据加载时的列识别,重复会导致数据覆盖
|
|
173
|
+
|
|
174
|
+
示例:
|
|
175
|
+
# 合法:列 ID 和列名都唯一
|
|
176
|
+
columns:
|
|
177
|
+
- id: col_1, name: id
|
|
178
|
+
- id: col_2, name: name
|
|
179
|
+
|
|
180
|
+
# 非法:列 ID 重复(会抛出 ValueError)
|
|
181
|
+
columns:
|
|
182
|
+
- id: col_1, name: id
|
|
183
|
+
- id: col_1, name: name
|
|
184
|
+
|
|
185
|
+
# 非法:列名重复(会抛出 ValueError)
|
|
186
|
+
columns:
|
|
187
|
+
- id: col_1, name: email
|
|
188
|
+
- id: col_2, name: email
|
|
189
|
+
"""
|
|
190
|
+
ids = [c.id for c in self.columns]
|
|
191
|
+
if len(set(ids)) != len(ids):
|
|
192
|
+
raise ValueError("columns.id 必须唯一")
|
|
193
|
+
|
|
194
|
+
names = [c.name for c in self.columns]
|
|
195
|
+
if len(set(names)) != len(names):
|
|
196
|
+
raise ValueError("columns.name 必须唯一")
|
|
197
|
+
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
@model_validator(mode="after")
|
|
201
|
+
def _validate_constraints_unique(self) -> TableSchemaFile:
|
|
202
|
+
"""@methoddesc 验证约束 ID 的唯一性
|
|
203
|
+
|
|
204
|
+
业务规则:
|
|
205
|
+
- 同一表内的所有约束 ID 必须唯一
|
|
206
|
+
|
|
207
|
+
校验逻辑:
|
|
208
|
+
1. 提取所有约束的 id 列表
|
|
209
|
+
2. 使用 set 去重后比较长度,如果不相等说明有重复
|
|
210
|
+
|
|
211
|
+
为什么需要这个校验?
|
|
212
|
+
- 约束 ID 用于错误报告中标识具体的约束规则
|
|
213
|
+
- 重复的约束 ID 会导致无法区分是哪个约束触发了错误
|
|
214
|
+
|
|
215
|
+
示例:
|
|
216
|
+
# 合法:约束 ID 唯一
|
|
217
|
+
constraints:
|
|
218
|
+
- id: not_null_email, type: NotNull, column: email
|
|
219
|
+
- id: unique_name, type: Unique, columns: [name]
|
|
220
|
+
|
|
221
|
+
# 非法:约束 ID 重复(会抛出 ValueError)
|
|
222
|
+
constraints:
|
|
223
|
+
- id: check_email, type: NotNull, column: email
|
|
224
|
+
- id: check_email, type: Unique, column: email
|
|
225
|
+
"""
|
|
226
|
+
constraint_ids = [c.id for c in self.constraints]
|
|
227
|
+
if len(set(constraint_ids)) != len(constraint_ids):
|
|
228
|
+
raise ValueError("constraints.id 必须唯一")
|
|
229
|
+
|
|
230
|
+
return self
|