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,100 @@
|
|
|
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
|
+
定义 BuilderInput / BuilderResult / BuilderFn 协议,以及各构建器共享的辅助函数。
|
|
19
|
+
|
|
20
|
+
架构设计(参考前端 nodeDataBuilder/registry.ts 的可注册构建器模式):
|
|
21
|
+
- 每种约束类型的构建逻辑封装为独立 BuilderFn
|
|
22
|
+
- 共享的单列映射逻辑提取为 resolve_single_column,消除 NotNull/AllowedValues/
|
|
23
|
+
DateLogic/Range/Charset 之间的重复代码
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from collections.abc import Callable
|
|
29
|
+
from typing import TYPE_CHECKING, Any
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from ..schema.types import TableSchemaFile
|
|
33
|
+
from ..types import ConstraintFile
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BuilderInput:
|
|
37
|
+
"""构建器输入上下文(所有 BuilderFn 共享)。
|
|
38
|
+
|
|
39
|
+
封装 refs/params/const_id/column_map/schema_files,以及 Composite 递归所需的
|
|
40
|
+
create_child 回调(依赖注入,避免 builder 直接 import factory 形成循环依赖)。
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
const: ConstraintFile,
|
|
46
|
+
refs: dict[str, Any],
|
|
47
|
+
params: dict[str, Any],
|
|
48
|
+
column_name_by_table_id: dict[str, dict[str, str]],
|
|
49
|
+
schema_files: dict[str, TableSchemaFile],
|
|
50
|
+
create_child: Callable[[ConstraintFile, dict[str, TableSchemaFile]], tuple[Any | None, str | None]],
|
|
51
|
+
) -> None:
|
|
52
|
+
self.const = const
|
|
53
|
+
self.refs = refs
|
|
54
|
+
self.params = params
|
|
55
|
+
self.column_name_by_table_id = column_name_by_table_id
|
|
56
|
+
self.schema_files = schema_files
|
|
57
|
+
self.create_child = create_child
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def const_id(self) -> str:
|
|
61
|
+
return self.const.id
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# 构建结果:(kwargs, error)。成功时 error 为 None;失败时 kwargs 为空字典。
|
|
65
|
+
BuilderResult = tuple[dict[str, Any], str | None]
|
|
66
|
+
|
|
67
|
+
# 构建器函数签名:接收 BuilderInput,返回 BuilderResult
|
|
68
|
+
BuilderFn = Callable[[BuilderInput], BuilderResult]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def table_display_name(inp: BuilderInput, table_id: str) -> str:
|
|
72
|
+
"""构建期错误消息用的表显示名(schema 缺失或未命名时兜底原 ID)。"""
|
|
73
|
+
schema = inp.schema_files.get(table_id)
|
|
74
|
+
return (schema.name if schema is not None else None) or table_id
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def resolve_single_column(
|
|
78
|
+
inp: BuilderInput,
|
|
79
|
+
table_key: str = "table_id",
|
|
80
|
+
col_key: str = "column_id",
|
|
81
|
+
) -> BuilderResult:
|
|
82
|
+
"""单列映射共享逻辑(NotNull/AllowedValues/DateLogic/Range/Charset 共用)。
|
|
83
|
+
|
|
84
|
+
从 refs 提取 table_id 和 column_id,映射为 table_id(保持稳定)和 column_name。
|
|
85
|
+
映射失败时返回错误。
|
|
86
|
+
"""
|
|
87
|
+
table_id = inp.refs.get(table_key)
|
|
88
|
+
col_id = inp.refs.get(col_key)
|
|
89
|
+
if table_id is None:
|
|
90
|
+
return {}, f"缺少 {table_key}"
|
|
91
|
+
if col_id is None:
|
|
92
|
+
return {}, f"缺少 {col_key}"
|
|
93
|
+
|
|
94
|
+
col_name = inp.column_name_by_table_id.get(table_id, {}).get(col_id)
|
|
95
|
+
if col_name is None:
|
|
96
|
+
# 表此时必然存在(factory 已做过存在性检查),用显示名报错便于定位;
|
|
97
|
+
# 兜底保留 ID 防御上游裸调用路径
|
|
98
|
+
return {}, f"引用的列 '{col_id}' 不存在于表 '{table_display_name(inp, table_id)}' 中"
|
|
99
|
+
|
|
100
|
+
return {"table": table_id, "column": col_name}, None
|
|
@@ -0,0 +1,77 @@
|
|
|
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 Composite 复合约束构建器
|
|
17
|
+
|
|
18
|
+
递归创建子约束(通过 BuilderInput.create_child 回调,避免直接 import factory 形成循环依赖)。
|
|
19
|
+
禁止嵌套 Composite;子约束用 model_validate 正常校验;子错误聚合为 warning。
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
|
|
26
|
+
from ..registry import normalize_constraint_type
|
|
27
|
+
from ..types import ConstraintFile
|
|
28
|
+
from .base import BuilderInput, BuilderResult
|
|
29
|
+
from .registry import register_builder
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@register_builder("Composite")
|
|
35
|
+
def build_composite(inp: BuilderInput) -> BuilderResult:
|
|
36
|
+
"""Composite: params {logic, sub_constraints: [ConstraintFile-like dict]},refs 原样保留。"""
|
|
37
|
+
params = inp.params
|
|
38
|
+
refs = inp.refs
|
|
39
|
+
sub_configs = params.get("sub_constraints", [])
|
|
40
|
+
sub_constraints = []
|
|
41
|
+
sub_errors: list[str] = []
|
|
42
|
+
|
|
43
|
+
for sub_cfg in sub_configs:
|
|
44
|
+
sub_type = normalize_constraint_type(sub_cfg.get("type", ""))
|
|
45
|
+
if sub_type == "Composite":
|
|
46
|
+
# 禁止递归嵌套 Composite;计入子错误而非静默跳过
|
|
47
|
+
sub_errors.append("不允许嵌套 Composite 子约束")
|
|
48
|
+
continue
|
|
49
|
+
# 使用 model_validate 正常构造(执行 Pydantic 校验),
|
|
50
|
+
# 过去用 model_construct 跳过校验导致非法子约束被静默接受
|
|
51
|
+
try:
|
|
52
|
+
sub_file = ConstraintFile.model_validate(sub_cfg)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
sub_errors.append(f"子约束 '{sub_cfg.get('id', '?')}' 配置非法: {e}")
|
|
55
|
+
continue
|
|
56
|
+
# 通过依赖注入的回调创建子约束(不直接 import factory.create_constraint)
|
|
57
|
+
sub_constraint, sub_error = inp.create_child(sub_file, inp.schema_files)
|
|
58
|
+
if sub_constraint is not None:
|
|
59
|
+
sub_constraints.append(sub_constraint)
|
|
60
|
+
elif sub_error is not None:
|
|
61
|
+
# 收集子错误而非静默忽略(过去注释"忽略即可"掩盖了真实问题)
|
|
62
|
+
sub_errors.append(f"子约束 '{sub_file.id}' 创建失败: {sub_error}")
|
|
63
|
+
# sub_error 为 None 且 sub_constraint 为 None:未启用,正常跳过
|
|
64
|
+
|
|
65
|
+
if sub_errors:
|
|
66
|
+
# 存在任何子错误即整条复合约束构建失败(fail-closed):复合是 all-or-nothing
|
|
67
|
+
# 的逻辑单元,缺一个子约束都会改变语义(all 少一项 → 假通过;any 多一项 → 假失败)。
|
|
68
|
+
# 返回 error 交由工厂跳过该约束并写入项目 warnings,对用户可见。
|
|
69
|
+
# 原实现仅 logger.warning 且返回空 sub_constraints——约束静默空过、客户端不可见。
|
|
70
|
+
logger.warning(f"Composite 约束 '{inp.const_id}' 子约束存在错误: {'; '.join(sub_errors)}")
|
|
71
|
+
return {}, f"子约束配置非法已跳过整条约束: {'; '.join(sub_errors)}"
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
"sub_constraints": sub_constraints,
|
|
75
|
+
"logic": params.get("logic", "all"),
|
|
76
|
+
"refs": refs,
|
|
77
|
+
}, None
|
|
@@ -0,0 +1,67 @@
|
|
|
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 Conditional 约束构建器
|
|
17
|
+
|
|
18
|
+
THEN 列 + IF 条件列表逐项映射。条件列支持回退用 column_id(transform 派生列不在 schema 中)。
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from .base import BuilderInput, BuilderResult
|
|
26
|
+
from .registry import register_builder
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@register_builder("Conditional")
|
|
30
|
+
def build_conditional(inp: BuilderInput) -> BuilderResult:
|
|
31
|
+
"""Conditional: refs {table_id, then_column_id, if_conditions, if_logic},params {then_condition}。"""
|
|
32
|
+
refs = inp.refs
|
|
33
|
+
table_id = refs.get("table_id")
|
|
34
|
+
then_col_id = refs.get("then_column_id")
|
|
35
|
+
if table_id is None:
|
|
36
|
+
return {}, "缺少 table_id"
|
|
37
|
+
if then_col_id is None:
|
|
38
|
+
return {}, "缺少 then_column_id"
|
|
39
|
+
if_logic = refs.get("if_logic", "and")
|
|
40
|
+
if_conditions = refs.get("if_conditions") or []
|
|
41
|
+
|
|
42
|
+
col_map = inp.column_name_by_table_id.get(table_id, {})
|
|
43
|
+
# 列名映射:优先从 schema 查找,找不到则直接使用 column_id
|
|
44
|
+
# (支持 transform 生成的派生列,这些列不在原始 schema 中)
|
|
45
|
+
then_col_name = col_map.get(then_col_id) or then_col_id
|
|
46
|
+
|
|
47
|
+
# 处理 IF 条件列表,将 column_id 映射为 column_name
|
|
48
|
+
normalized_conditions: list[dict[str, Any]] = []
|
|
49
|
+
for cond in if_conditions:
|
|
50
|
+
if_col_id = cond.get("if_column_id")
|
|
51
|
+
if_col_name = col_map.get(if_col_id) or if_col_id
|
|
52
|
+
normalized_conditions.append(
|
|
53
|
+
{
|
|
54
|
+
"if_column": if_col_name,
|
|
55
|
+
"operator": cond.get("operator", "eq"),
|
|
56
|
+
"value": cond.get("value"),
|
|
57
|
+
"values": cond.get("values"),
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
"table": table_id,
|
|
63
|
+
"then_column": then_col_name,
|
|
64
|
+
"then_condition": inp.params.get("then_condition"),
|
|
65
|
+
"if_conditions": normalized_conditions,
|
|
66
|
+
"if_logic": if_logic,
|
|
67
|
+
}, None
|
|
@@ -0,0 +1,53 @@
|
|
|
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 ForeignKey 约束构建器
|
|
17
|
+
|
|
18
|
+
双向引用(from/to 两端表+列),与其他单端约束不同。
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from .base import BuilderInput, BuilderResult, table_display_name
|
|
24
|
+
from .registry import register_builder
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@register_builder("ForeignKey")
|
|
28
|
+
def build_foreign_key(inp: BuilderInput) -> BuilderResult:
|
|
29
|
+
"""ForeignKey: refs {from_table_id, from_column_id, to_table_id, to_column_id}。"""
|
|
30
|
+
refs = inp.refs
|
|
31
|
+
from_table_id = refs.get("from_table_id")
|
|
32
|
+
from_col_id = refs.get("from_column_id")
|
|
33
|
+
to_table_id = refs.get("to_table_id")
|
|
34
|
+
to_col_id = refs.get("to_column_id")
|
|
35
|
+
|
|
36
|
+
if from_table_id is None or to_table_id is None:
|
|
37
|
+
return {}, "缺少必要的表引用"
|
|
38
|
+
|
|
39
|
+
from_col_name = inp.column_name_by_table_id.get(from_table_id, {}).get(str(from_col_id))
|
|
40
|
+
to_col_name = inp.column_name_by_table_id.get(to_table_id, {}).get(str(to_col_id))
|
|
41
|
+
|
|
42
|
+
if from_col_name is None:
|
|
43
|
+
# 报错用表显示名(from 侧经 factory 存在性检查;to 侧可能悬空,兜底 ID)
|
|
44
|
+
return {}, f"引用的列 '{from_col_id}' 不存在于表 '{table_display_name(inp, from_table_id)}' 中"
|
|
45
|
+
if to_col_name is None:
|
|
46
|
+
return {}, f"引用的列 '{to_col_id}' 不存在于表 '{table_display_name(inp, to_table_id)}' 中"
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
"from_table": from_table_id,
|
|
50
|
+
"from_column": from_col_name,
|
|
51
|
+
"to_table": to_table_id,
|
|
52
|
+
"to_column": to_col_name,
|
|
53
|
+
}, None
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
核心入口:build_kwargs(type_name, inp) → BuilderResult
|
|
19
|
+
各约束类型通过 register_builder 装饰器注册到 CONSTRAINT_BUILDERS 字典。
|
|
20
|
+
未注册的类型返回 (None, None),由 factory 走通用降级路径。
|
|
21
|
+
|
|
22
|
+
设计(参考前端 nodeDataBuilder/registry.ts):
|
|
23
|
+
- 注册表是 dict[type_name, BuilderFn]
|
|
24
|
+
- 新增约束类型 = 在对应 builder 文件加一个 @register_builder,其余自动派生
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from collections.abc import Callable
|
|
30
|
+
from typing import TYPE_CHECKING
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from .base import BuilderFn, BuilderInput, BuilderResult
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# 构建器注册表:type_name -> BuilderFn
|
|
37
|
+
CONSTRAINT_BUILDERS: dict[str, BuilderFn] = {}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def register_builder(type_name: str) -> Callable[[BuilderFn], BuilderFn]:
|
|
41
|
+
"""装饰器:注册约束构建器到 CONSTRAINT_BUILDERS。
|
|
42
|
+
|
|
43
|
+
用法:
|
|
44
|
+
@register_builder("NotNull")
|
|
45
|
+
def build_not_null(inp: BuilderInput) -> BuilderResult:
|
|
46
|
+
...
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def decorator(fn: BuilderFn) -> BuilderFn:
|
|
50
|
+
CONSTRAINT_BUILDERS[type_name] = fn
|
|
51
|
+
return fn
|
|
52
|
+
|
|
53
|
+
return decorator
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def build_kwargs(type_name: str, inp: BuilderInput) -> BuilderResult | None:
|
|
57
|
+
"""主入口:根据约束类型查找构建器并执行。
|
|
58
|
+
|
|
59
|
+
:return: (kwargs, error) 元组;若类型未注册返回 None(调用方走通用降级)
|
|
60
|
+
"""
|
|
61
|
+
builder = CONSTRAINT_BUILDERS.get(type_name)
|
|
62
|
+
if builder is None:
|
|
63
|
+
return None
|
|
64
|
+
return builder(inp)
|
|
@@ -0,0 +1,51 @@
|
|
|
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 Scripted 约束构建器
|
|
17
|
+
|
|
18
|
+
column_id 可选(表达式可不绑定到具体列)。
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from .base import BuilderInput, BuilderResult
|
|
24
|
+
from .registry import register_builder
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@register_builder("Scripted")
|
|
28
|
+
def build_scripted(inp: BuilderInput) -> BuilderResult:
|
|
29
|
+
"""Scripted: refs {table_id, column_id?},params {name, expression}。"""
|
|
30
|
+
refs = inp.refs
|
|
31
|
+
table_id = refs.get("table_id")
|
|
32
|
+
col_id = refs.get("column_id")
|
|
33
|
+
|
|
34
|
+
if table_id is None:
|
|
35
|
+
# 配置残缺 fail-fast:与 base.resolve_single_column 的"缺少 table_id"同口径,
|
|
36
|
+
# 不再以 table=None 静默构建出语义偷换的约束
|
|
37
|
+
return {}, "缺少 table_id"
|
|
38
|
+
|
|
39
|
+
kwargs: dict[str, object] = {"table": table_id}
|
|
40
|
+
# 使用约束 ID 作为默认名称
|
|
41
|
+
kwargs["name"] = inp.params.get("name", inp.const_id)
|
|
42
|
+
kwargs["expression"] = inp.params.get("expression", "")
|
|
43
|
+
# column_id 是可选的;一旦配置则必须映射成功——原实现 .get 链返回 None 时
|
|
44
|
+
# 照样写入 kwargs(column=None 即整行脚本语义),列改名后的失效引用被静默降级
|
|
45
|
+
if col_id:
|
|
46
|
+
col_name = inp.column_name_by_table_id.get(table_id, {}).get(str(col_id))
|
|
47
|
+
if col_name is None:
|
|
48
|
+
return {}, f"引用的列 '{col_id}' 不存在于表 '{table_id}' 中"
|
|
49
|
+
kwargs["column"] = col_name
|
|
50
|
+
|
|
51
|
+
return kwargs, None # type: ignore[return-value]
|
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
NotNull / AllowedValues / DateLogic / Range / Charset 共享"单列映射"结构,
|
|
19
|
+
区别仅在于从 params 提取的额外字段。本模块用统一的 build_single_column 加上各类型的
|
|
20
|
+
params 适配器,消除原 factory.py 中 4 处近乎逐行重复的代码。
|
|
21
|
+
|
|
22
|
+
Charset 此前无独立分支(走通用路径导致残缺实例),此处补全修复。
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from .base import BuilderInput, BuilderResult, resolve_single_column
|
|
28
|
+
from .registry import register_builder
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@register_builder("NotNull")
|
|
32
|
+
def build_not_null(inp: BuilderInput) -> BuilderResult:
|
|
33
|
+
"""NotNull: refs {table_id, column_id},无 params。"""
|
|
34
|
+
return resolve_single_column(inp)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@register_builder("AllowedValues")
|
|
38
|
+
def build_allowed_values(inp: BuilderInput) -> BuilderResult:
|
|
39
|
+
"""AllowedValues: refs {table_id, column_id},params {allowed_values}。"""
|
|
40
|
+
kwargs, error = resolve_single_column(inp)
|
|
41
|
+
if error:
|
|
42
|
+
return {}, error
|
|
43
|
+
kwargs["allowed_values"] = inp.params.get("allowed_values", [])
|
|
44
|
+
return kwargs, None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@register_builder("DateLogic")
|
|
48
|
+
def build_date_logic(inp: BuilderInput) -> BuilderResult:
|
|
49
|
+
"""DateLogic: refs {table_id, column_id},params 全量透传(filter_kwargs_for_class 兜底)。"""
|
|
50
|
+
kwargs, error = resolve_single_column(inp)
|
|
51
|
+
if error:
|
|
52
|
+
return {}, error
|
|
53
|
+
# 将 params 中的所有参数添加到 kwargs,filter_kwargs_for_class 会过滤不需要的
|
|
54
|
+
kwargs.update(inp.params)
|
|
55
|
+
return kwargs, None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@register_builder("Range")
|
|
59
|
+
def build_range(inp: BuilderInput) -> BuilderResult:
|
|
60
|
+
"""Range: refs {table_id, column_id},params {min→min_value, max→max_value, boundary_mode}。"""
|
|
61
|
+
kwargs, error = resolve_single_column(inp)
|
|
62
|
+
if error:
|
|
63
|
+
return {}, error
|
|
64
|
+
params = inp.params
|
|
65
|
+
# min -> min_value, max -> max_value 的字段重命名
|
|
66
|
+
if "min" in params:
|
|
67
|
+
kwargs["min_value"] = params["min"]
|
|
68
|
+
if "max" in params:
|
|
69
|
+
kwargs["max_value"] = params["max"]
|
|
70
|
+
if "boundary_mode" in params:
|
|
71
|
+
kwargs["boundary_mode"] = params["boundary_mode"]
|
|
72
|
+
return kwargs, None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@register_builder("Charset")
|
|
76
|
+
def build_charset(inp: BuilderInput) -> BuilderResult:
|
|
77
|
+
"""Charset: refs {table_id, column_id},params {charset_mode}。
|
|
78
|
+
|
|
79
|
+
修复:此前 factory.py 无 Charset 独立分支,走通用路径导致无 table/column 的残缺实例。
|
|
80
|
+
"""
|
|
81
|
+
kwargs, error = resolve_single_column(inp)
|
|
82
|
+
if error:
|
|
83
|
+
return {}, error
|
|
84
|
+
# charset_mode 默认 "ascii"(与 CharsetConstraint.__init__ 默认值一致)
|
|
85
|
+
kwargs["charset_mode"] = inp.params.get("charset_mode", "ascii")
|
|
86
|
+
return kwargs, None
|
|
@@ -0,0 +1,53 @@
|
|
|
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 Unique 约束构建器
|
|
17
|
+
|
|
18
|
+
Unique 的 column 支持 list 语义(多列联合唯一),与其他单列约束不同,需独立构建器。
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from .base import BuilderInput, BuilderResult
|
|
26
|
+
from .registry import register_builder
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@register_builder("Unique")
|
|
30
|
+
def build_unique(inp: BuilderInput) -> BuilderResult:
|
|
31
|
+
"""Unique: refs {table_id, column_ids | column_id}。
|
|
32
|
+
|
|
33
|
+
column_ids 支持列表(多列)或字符串(单列兼容)。
|
|
34
|
+
"""
|
|
35
|
+
refs = inp.refs
|
|
36
|
+
table_id = refs.get("table_id")
|
|
37
|
+
col_id = refs.get("column_ids") or refs.get("column_id")
|
|
38
|
+
if not isinstance(table_id, str):
|
|
39
|
+
return {}, "缺少 table_id"
|
|
40
|
+
# 确保 col_id 是列表
|
|
41
|
+
if isinstance(col_id, str):
|
|
42
|
+
col_id = [col_id]
|
|
43
|
+
elif not col_id:
|
|
44
|
+
col_id = []
|
|
45
|
+
|
|
46
|
+
kwargs: dict[str, Any] = {"table": table_id}
|
|
47
|
+
# 映射 column_ids -> column_names
|
|
48
|
+
mapped_cols = [inp.column_name_by_table_id.get(table_id, {}).get(str(cid)) for cid in col_id]
|
|
49
|
+
if None in mapped_cols:
|
|
50
|
+
invalid_cols = [cid for cid, name in zip(col_id, mapped_cols) if name is None]
|
|
51
|
+
return {}, f"引用的列不存在: {invalid_cols}"
|
|
52
|
+
kwargs["column"] = mapped_cols
|
|
53
|
+
return kwargs, None
|