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,170 @@
|
|
|
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
|
+
- 根据 ConstraintFile 配置创建运行时约束对象
|
|
21
|
+
- 处理约束引用解析(table_id -> table_name, column_id -> column_name)
|
|
22
|
+
- 支持批量创建多个约束对象
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- 工厂模式: 根据约束类型名称动态实例化对应约束类
|
|
26
|
+
- 可注册构建器: 各约束类型的参数构建逻辑封装在 builders/ 子包中,
|
|
27
|
+
通过 @register_builder 自注册(参考前端 nodeDataBuilder 模式)
|
|
28
|
+
- 依赖注册表: 使用 registry.py 获取约束类型到实现类的映射
|
|
29
|
+
- 参数过滤: 使用 filter_kwargs_for_class 过滤仅构造函数接受的参数
|
|
30
|
+
|
|
31
|
+
输入示例:
|
|
32
|
+
constraint = ConstraintFile(
|
|
33
|
+
version=2,
|
|
34
|
+
id="unique_email",
|
|
35
|
+
type="Unique",
|
|
36
|
+
enabled=True,
|
|
37
|
+
refs={"table_id": "users", "column_ids": ["email"]},
|
|
38
|
+
params={},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
输出示例:
|
|
42
|
+
runtime_constraint, error = create_constraint(constraint, schema_files)
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
import logging
|
|
48
|
+
from typing import TYPE_CHECKING, Any
|
|
49
|
+
|
|
50
|
+
from ..schema.types_parts.column_utils import build_column_id_to_name_map
|
|
51
|
+
|
|
52
|
+
logger = logging.getLogger(__name__)
|
|
53
|
+
|
|
54
|
+
# 导入约束注册表和工具函数,用于根据类型名称查找约束类并过滤参数
|
|
55
|
+
# side-effect import 触发各 builder 的 @register_builder 注册
|
|
56
|
+
from . import builders as _builders # noqa: F401
|
|
57
|
+
from .builders import BuilderInput, build_kwargs
|
|
58
|
+
from .registry import filter_kwargs_for_class, normalize_constraint_type, resolve_constraint_class
|
|
59
|
+
from .types import ConstraintFile
|
|
60
|
+
|
|
61
|
+
if TYPE_CHECKING:
|
|
62
|
+
# 仅在类型检查阶段导入 Schema 类型,避免运行时循环依赖
|
|
63
|
+
from ..schema.types import TableSchemaFile
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def create_constraint(
|
|
67
|
+
const: ConstraintFile,
|
|
68
|
+
schema_files: dict[str, TableSchemaFile],
|
|
69
|
+
) -> tuple[Any | None, str | None]:
|
|
70
|
+
"""
|
|
71
|
+
@methoddesc 根据 ConstraintFile 创建运行时约束对象。
|
|
72
|
+
|
|
73
|
+
该函数执行以下步骤:
|
|
74
|
+
1. 检查约束是否启用,未启用则返回 None
|
|
75
|
+
2. 规范化约束类型名称,从注册表获取对应的约束类
|
|
76
|
+
3. 构建 column_id -> column_name 映射 + BuilderInput 上下文
|
|
77
|
+
4. 调用 build_kwargs 委托给各类型的注册构建器(消除 if-elif 链)
|
|
78
|
+
5. 过滤参数,只保留约束类构造函数接受的参数
|
|
79
|
+
6. 实例化约束类并返回
|
|
80
|
+
|
|
81
|
+
:param const: ConstraintFile 配置对象,包含约束的完整配置
|
|
82
|
+
:param schema_files: schema 文件字典,键为 table_id,值为 TableSchemaFile 对象
|
|
83
|
+
:return: (约束实例对象, 错误信息)。若成功,错误信息为 None;若失败,实例为 None;若未启用,两者均为 None。
|
|
84
|
+
"""
|
|
85
|
+
# 步骤1:检查约束是否启用,未启用则跳过
|
|
86
|
+
if not const.enabled:
|
|
87
|
+
return None, None
|
|
88
|
+
|
|
89
|
+
# 步骤2:规范化约束类型名称(如 "unique" -> "Unique"),从注册表延迟解析对应的约束类
|
|
90
|
+
type_name = normalize_constraint_type(const.type)
|
|
91
|
+
constraint_class = resolve_constraint_class(type_name)
|
|
92
|
+
if constraint_class is None:
|
|
93
|
+
return None, f"不支持的约束类型: {const.type}(规范化后: {type_name})"
|
|
94
|
+
|
|
95
|
+
refs = const.refs or {}
|
|
96
|
+
params = const.params or {}
|
|
97
|
+
|
|
98
|
+
# 构建 column_id -> column_name 映射表(按 table_id 分组,递归含嵌套 children)
|
|
99
|
+
# 结构:{table_id: {column_id: column_name, ...}, ...}
|
|
100
|
+
# 注:递归遍历确保 JSON 嵌套子列上的约束也能解析列引用
|
|
101
|
+
column_name_by_table_id: dict[str, dict[str, str]] = {
|
|
102
|
+
sid: build_column_id_to_name_map(s.columns) for sid, s in schema_files.items()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# 检查约束引用的表是否存在
|
|
106
|
+
table_id = refs.get("table_id") or refs.get("from_table_id")
|
|
107
|
+
if table_id and table_id not in schema_files:
|
|
108
|
+
return None, f"引用的表 '{table_id}' 不存在(可能已被删除)"
|
|
109
|
+
|
|
110
|
+
# 步骤3-4:构建 BuilderInput 并委托给注册构建器(Composite 的递归通过 create_child 回调注入)
|
|
111
|
+
inp = BuilderInput(
|
|
112
|
+
const=const,
|
|
113
|
+
refs=refs,
|
|
114
|
+
params=params,
|
|
115
|
+
column_name_by_table_id=column_name_by_table_id,
|
|
116
|
+
schema_files=schema_files,
|
|
117
|
+
create_child=create_constraint, # 依赖注入:Composite builder 用此回调递归,避免循环 import
|
|
118
|
+
)
|
|
119
|
+
result = build_kwargs(type_name, inp)
|
|
120
|
+
if result is None:
|
|
121
|
+
# 未注册的类型:返回错误(此前 Charset 走通用路径是隐性 bug,现已补 builder)
|
|
122
|
+
return None, f"不支持的约束类型: {const.type}(规范化后: {type_name})"
|
|
123
|
+
kwargs, error = result
|
|
124
|
+
if error:
|
|
125
|
+
return None, error
|
|
126
|
+
|
|
127
|
+
# 步骤5:过滤 kwargs,只保留约束类构造函数接受的参数
|
|
128
|
+
filtered = filter_kwargs_for_class(constraint_class, kwargs)
|
|
129
|
+
|
|
130
|
+
# 步骤6:实例化约束类并返回
|
|
131
|
+
return constraint_class(**filtered), None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def create_constraints(
|
|
135
|
+
constraint_files: dict[str, ConstraintFile],
|
|
136
|
+
schema_files: dict[str, TableSchemaFile],
|
|
137
|
+
) -> tuple[list[Any], list[str]]:
|
|
138
|
+
"""
|
|
139
|
+
@methoddesc 批量创建运行时约束对象。
|
|
140
|
+
|
|
141
|
+
遍历所有约束配置文件,调用 create_constraint 进行转换,
|
|
142
|
+
过滤掉未启用的约束,返回所有启用的约束实例列表。
|
|
143
|
+
|
|
144
|
+
:param constraint_files: 约束配置字典,键为 constraint_id,值为 ConstraintFile 对象
|
|
145
|
+
:param schema_files: schema 文件字典,键为 table_id,值为 TableSchemaFile 对象
|
|
146
|
+
:return: (约束实例列表, 警告信息列表)
|
|
147
|
+
"""
|
|
148
|
+
# 初始化结果列表
|
|
149
|
+
constraints: list[Any] = []
|
|
150
|
+
warnings: list[str] = []
|
|
151
|
+
|
|
152
|
+
# 遍历所有约束配置
|
|
153
|
+
for const in constraint_files.values():
|
|
154
|
+
try:
|
|
155
|
+
# 创建单个约束实例
|
|
156
|
+
constraint, error = create_constraint(const, schema_files)
|
|
157
|
+
# 仅添加非 None 的约束(即已启用的约束)
|
|
158
|
+
if constraint is not None:
|
|
159
|
+
# P0-3: 在实例上挂约束 ID(运行时对象不参与序列化回写),
|
|
160
|
+
# 供调用方(executor)将错误回溯到来源约束文件
|
|
161
|
+
constraint.constraint_id = const.id # type: ignore[attr-defined]
|
|
162
|
+
constraints.append(constraint)
|
|
163
|
+
elif error:
|
|
164
|
+
# 收集警告信息
|
|
165
|
+
warnings.append(f"约束「{const.description or const.id}」配置有误({error}),本次校验已跳过它")
|
|
166
|
+
# else: 约束未启用,忽略
|
|
167
|
+
except Exception as e:
|
|
168
|
+
warnings.append(f"约束「{const.description or const.id}」加载时出现异常({str(e)}),本次校验已跳过它")
|
|
169
|
+
|
|
170
|
+
return constraints, warnings
|
|
@@ -0,0 +1,214 @@
|
|
|
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
|
+
- 从 *.constraint.yaml 文件读取约束配置
|
|
21
|
+
- 验证约束配置文件的格式和内容
|
|
22
|
+
- 支持批量加载多个约束配置文件
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- 读取层: 将 YAML 配置文件解析为程序可用的 Python 对象
|
|
26
|
+
- 数据验证: 使用 Pydantic 确保配置文件格式正确
|
|
27
|
+
- 读写配合: 与 writer.py 配合实现约束配置全生命周期管理
|
|
28
|
+
|
|
29
|
+
输入示例:
|
|
30
|
+
# users.constraint.yaml
|
|
31
|
+
version: 2
|
|
32
|
+
id: unique_email
|
|
33
|
+
type: Unique
|
|
34
|
+
enabled: true
|
|
35
|
+
refs:
|
|
36
|
+
table_id: users
|
|
37
|
+
column_ids: [email]
|
|
38
|
+
|
|
39
|
+
输出示例:
|
|
40
|
+
constraint = load_constraint("users.constraint.yaml")
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
from typing import TYPE_CHECKING
|
|
47
|
+
|
|
48
|
+
from pydantic import ValidationError
|
|
49
|
+
|
|
50
|
+
# 导入底层的 YAML 读取工具
|
|
51
|
+
from app.shared.core.io.yaml import read_yaml
|
|
52
|
+
|
|
53
|
+
# 导入约束配置的数据模型
|
|
54
|
+
from .types import ConstraintFile
|
|
55
|
+
|
|
56
|
+
if TYPE_CHECKING:
|
|
57
|
+
# 仅在类型检查阶段导入,避免运行时循环依赖
|
|
58
|
+
from .types import ConstraintFile
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def load_constraint(constraint_path: str | Path) -> ConstraintFile:
|
|
62
|
+
"""
|
|
63
|
+
@methoddesc 从 YAML 文件加载约束配置。
|
|
64
|
+
|
|
65
|
+
============================================================================
|
|
66
|
+
配置文件示例 (本函数处理的配置文件长这样)
|
|
67
|
+
============================================================================
|
|
68
|
+
本函数处理以下格式的 *.constraint.yaml 文件:
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
# ============================================================
|
|
72
|
+
# 约束规则配置文件 (*.constraint.yaml)
|
|
73
|
+
# ============================================================
|
|
74
|
+
|
|
75
|
+
version: 2
|
|
76
|
+
|
|
77
|
+
id: unique_user_email
|
|
78
|
+
|
|
79
|
+
type: Unique
|
|
80
|
+
|
|
81
|
+
enabled: true
|
|
82
|
+
|
|
83
|
+
description: "用户邮箱必须唯一"
|
|
84
|
+
|
|
85
|
+
refs:
|
|
86
|
+
table_id: users
|
|
87
|
+
column_ids: [email]
|
|
88
|
+
|
|
89
|
+
params: {}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
============================================================================
|
|
93
|
+
业务场景 (什么情况下会调用这个函数)
|
|
94
|
+
============================================================================
|
|
95
|
+
- 场景1: 项目启动时加载约束配置
|
|
96
|
+
系统启动时需要加载所有约束配置文件,构建运行时约束对象。
|
|
97
|
+
|
|
98
|
+
- 场景2: 加载单个约束详情
|
|
99
|
+
用户在 UI 上查看某个约束的详细信息。
|
|
100
|
+
|
|
101
|
+
- 场景3: 约束配置导入验证
|
|
102
|
+
导入约束配置文件时,需要验证格式是否正确。
|
|
103
|
+
|
|
104
|
+
============================================================================
|
|
105
|
+
数据流 (输入如何变成输出)
|
|
106
|
+
============================================================================
|
|
107
|
+
输入参数:
|
|
108
|
+
- constraint_path: str | Path,constraint 文件路径
|
|
109
|
+
示例值: "constraints/unique_user_email.constraint.yaml"
|
|
110
|
+
|
|
111
|
+
处理步骤:
|
|
112
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
113
|
+
│ Step 1: 路径标准化 │
|
|
114
|
+
├─────────────────────────────────────────────────────────────┤
|
|
115
|
+
│ 输入: 字符串或 Path 对象 │
|
|
116
|
+
│ 操作: 转换为 Path 对象 │
|
|
117
|
+
│ 输出: Path 对象 │
|
|
118
|
+
└─────────────────────────────────────────────────────────────┘
|
|
119
|
+
|
|
120
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
121
|
+
│ Step 2: YAML 解析 │
|
|
122
|
+
├─────────────────────────────────────────────────────────────┤
|
|
123
|
+
│ 输入: Path 对象 │
|
|
124
|
+
│ 操作: 读取 YAML 文件 │
|
|
125
|
+
│ 输出: 字典对象 │
|
|
126
|
+
└─────────────────────────────────────────────────────────────┘
|
|
127
|
+
|
|
128
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
129
|
+
│ Step 3: Pydantic 验证 │
|
|
130
|
+
├─────────────────────────────────────────────────────────────┤
|
|
131
|
+
│ 输入: 字典对象 │
|
|
132
|
+
│ 操作: 验证数据结构 │
|
|
133
|
+
│ 输出: ConstraintFile 对象 │
|
|
134
|
+
└─────────────────────────────────────────────────────────────┘
|
|
135
|
+
|
|
136
|
+
最终输出: ConstraintFile 对象
|
|
137
|
+
|
|
138
|
+
============================================================================
|
|
139
|
+
异常处理
|
|
140
|
+
============================================================================
|
|
141
|
+
| 异常类型 | 触发条件 | 处理方式 |
|
|
142
|
+
|---------|---------|---------|
|
|
143
|
+
| FileNotFoundError | 文件不存在 | 检查文件路径 |
|
|
144
|
+
| ValueError | YAML 格式错误 | 验证 YAML 文件 |
|
|
145
|
+
| ValueError | 验证失败 | 检查配置格式 |
|
|
146
|
+
|
|
147
|
+
:param constraint_path: constraint 文件路径,可以是字符串或 Path 对象
|
|
148
|
+
:return: ConstraintFile 对象,包含验证后的约束配置数据
|
|
149
|
+
:raises ValueError: 文件不存在、格式错误或验证失败时抛出
|
|
150
|
+
:raises FileNotFoundError: 文件路径不存在时由 read_yaml 抛出
|
|
151
|
+
"""
|
|
152
|
+
# 步骤1:标准化路径格式,兼容字符串和 Path 对象输入
|
|
153
|
+
path = Path(constraint_path)
|
|
154
|
+
# 步骤2:读取 YAML 文件原始内容(字典格式)
|
|
155
|
+
raw = read_yaml(path)
|
|
156
|
+
# 步骤3:使用 Pydantic 验证数据格式,确保必填字段存在且类型正确
|
|
157
|
+
try:
|
|
158
|
+
return ConstraintFile.model_validate(raw)
|
|
159
|
+
except ValidationError as e:
|
|
160
|
+
# 步骤4:验证失败时抛出带详细信息的 ValueError
|
|
161
|
+
raise ValueError(f"constraint 校验失败: {constraint_path}\n{e}") from e
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def load_constraints(constraint_paths: list) -> dict[str, ConstraintFile]:
|
|
165
|
+
"""
|
|
166
|
+
@methoddesc 批量加载约束配置。
|
|
167
|
+
|
|
168
|
+
============================================================================
|
|
169
|
+
业务场景 (什么情况下会调用这个函数)
|
|
170
|
+
============================================================================
|
|
171
|
+
- 场景1: 项目启动时加载所有约束
|
|
172
|
+
系统启动时需要批量加载项目中所有的约束配置文件。
|
|
173
|
+
|
|
174
|
+
- 场景2: 批量导入约束
|
|
175
|
+
用户可以一次性导入多个约束配置文件。
|
|
176
|
+
|
|
177
|
+
============================================================================
|
|
178
|
+
数据流 (输入如何变成输出)
|
|
179
|
+
============================================================================
|
|
180
|
+
输入参数:
|
|
181
|
+
- constraint_paths: list,constraint 文件路径列表
|
|
182
|
+
示例值: [
|
|
183
|
+
"constraints/unique_email.constraint.yaml",
|
|
184
|
+
"constraints/gender_allowed.constraint.yaml"
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
处理步骤:
|
|
188
|
+
遍历路径列表,依次调用 load_constraint 加载每个约束
|
|
189
|
+
|
|
190
|
+
最终输出: Dict[str, ConstraintFile],约束 ID -> ConstraintFile 映射
|
|
191
|
+
示例:
|
|
192
|
+
constraints = load_constraints(paths)
|
|
193
|
+
print(constraints.keys()) # dict_keys(['unique_email', 'gender_allowed'])
|
|
194
|
+
|
|
195
|
+
============================================================================
|
|
196
|
+
异常处理
|
|
197
|
+
============================================================================
|
|
198
|
+
| 异常类型 | 触发条件 | 处理方式 |
|
|
199
|
+
|---------|---------|---------|
|
|
200
|
+
| ValueError | 单个文件加载失败 | 停止并抛出异常 |
|
|
201
|
+
|
|
202
|
+
:param constraint_paths: constraint 文件路径列表,每个元素为文件路径
|
|
203
|
+
:return: 约束配置字典,键为 constraint.id(约束ID),值为 ConstraintFile 对象
|
|
204
|
+
:raises ValueError: 任一文件加载或验证失败时抛出
|
|
205
|
+
"""
|
|
206
|
+
# 初始化结果字典,使用 constraint_id 作为键便于后续快速查找
|
|
207
|
+
constraints: dict[str, ConstraintFile] = {}
|
|
208
|
+
# 遍历所有文件路径,依次加载每个约束配置
|
|
209
|
+
for path in constraint_paths:
|
|
210
|
+
# 加载单个约束配置(包含验证)
|
|
211
|
+
constraint = load_constraint(path)
|
|
212
|
+
# 以约束ID为键存储,支持快速通过ID获取约束配置
|
|
213
|
+
constraints[constraint.id] = constraint
|
|
214
|
+
return constraints
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
"""
|
|
17
|
+
@fileoverview 约束类型注册表模块
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 提供约束类型名称到约束类的映射
|
|
21
|
+
- 支持约束类型别名(多种命名风格标准化)
|
|
22
|
+
- 提供参数过滤功能(根据类构造函数签名过滤参数)
|
|
23
|
+
- 提供支持的约束类型列表(供 API/前端发现使用)
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- 注册中心模式: 使用字典维护约束类型与实现类的映射关系
|
|
27
|
+
- 名称标准化: 支持标准命名、下划线命名、驼峰命名等多种风格
|
|
28
|
+
- 参数过滤: 使用 inspect 模块获取类构造函数签名并过滤参数
|
|
29
|
+
|
|
30
|
+
重要约定:
|
|
31
|
+
- 为避免 core 层反向依赖 domain 层,本模块只保存约束类的导入路径字符串。
|
|
32
|
+
- 真正的约束类通过 resolve_constraint_class() 在运行时延迟解析,由 services 层使用。
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
# inspect 模块用于获取类构造函数的签名,实现参数过滤
|
|
38
|
+
import importlib
|
|
39
|
+
import inspect
|
|
40
|
+
from typing import Any
|
|
41
|
+
|
|
42
|
+
# ============================================================
|
|
43
|
+
# 约束类型注册表
|
|
44
|
+
# ============================================================
|
|
45
|
+
# 键:约束类型标准名称(首字母大写)
|
|
46
|
+
# 值:约束类在项目中的完整导入路径(字符串)
|
|
47
|
+
# 用于在 factory.py 中根据类型名称延迟解析对应的约束类
|
|
48
|
+
#
|
|
49
|
+
# 注意:此处不使用直接类引用,以保证 core 层不依赖 domain 层。
|
|
50
|
+
|
|
51
|
+
CONSTRAINT_REGISTRY: dict[str, str] = {
|
|
52
|
+
# 唯一约束:支持单列或多列组合唯一
|
|
53
|
+
"Unique": "app.shared.domain.validation_constraints.UniqueConstraint",
|
|
54
|
+
# 非空约束:列值不能为空
|
|
55
|
+
"NotNull": "app.shared.domain.validation_constraints.NotNullConstraint",
|
|
56
|
+
# 允许值约束:列值必须来自预定义集合
|
|
57
|
+
"AllowedValues": "app.shared.domain.validation_constraints.AllowedValuesConstraint",
|
|
58
|
+
# 外键约束:参照完整性校验
|
|
59
|
+
"ForeignKey": "app.shared.domain.validation_constraints.ForeignKeyConstraints",
|
|
60
|
+
# 区间约束:数值/日期范围校验
|
|
61
|
+
"Range": "app.shared.domain.validation_constraints.RangeConstraint",
|
|
62
|
+
# 条件约束:满足 IF 条件时执行 THEN 规则
|
|
63
|
+
"Conditional": "app.shared.domain.validation_constraints.ConditionalConstraint",
|
|
64
|
+
# 脚本约束:执行自定义表达式
|
|
65
|
+
"Scripted": "app.shared.domain.validation_constraints.ScriptedConstraint",
|
|
66
|
+
# 字符集约束:校验 ASCII 或中文字符
|
|
67
|
+
"Charset": "app.shared.domain.validation_constraints.CharsetConstraint",
|
|
68
|
+
# 日期逻辑约束:日期比较和计算
|
|
69
|
+
"DateLogic": "app.shared.domain.validation_constraints.DateLogicConstraint",
|
|
70
|
+
# 复合约束:包含多个子约束,按逻辑策略聚合结果
|
|
71
|
+
"Composite": "app.shared.domain.validation_constraints.CompositeConstraint",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ============================================================
|
|
76
|
+
# 约束类型别名映射表
|
|
77
|
+
# ============================================================
|
|
78
|
+
# 键:各种命名风格的类型名称
|
|
79
|
+
# 值:标准名称(首字母大写)
|
|
80
|
+
# 用于支持用户使用不同格式的类型名称
|
|
81
|
+
|
|
82
|
+
CONSTRAINT_TYPE_ALIASES: dict[str, str] = {
|
|
83
|
+
# 唯一约束别名
|
|
84
|
+
"unique": "Unique",
|
|
85
|
+
# 非空约束别名(多种拼写)
|
|
86
|
+
"not_null": "NotNull",
|
|
87
|
+
"notnull": "NotNull",
|
|
88
|
+
# 允许值约束别名
|
|
89
|
+
"allowed_values": "AllowedValues",
|
|
90
|
+
"allowedvalues": "AllowedValues",
|
|
91
|
+
# 外键约束别名
|
|
92
|
+
"foreign_key": "ForeignKey",
|
|
93
|
+
"foreignkey": "ForeignKey",
|
|
94
|
+
# 区间约束别名
|
|
95
|
+
"range": "Range",
|
|
96
|
+
# 条件约束别名
|
|
97
|
+
"conditional": "Conditional",
|
|
98
|
+
# 脚本约束别名
|
|
99
|
+
"scripted": "Scripted",
|
|
100
|
+
# 日期逻辑约束别名
|
|
101
|
+
"datelogic": "DateLogic",
|
|
102
|
+
"date_logic": "DateLogic",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def normalize_constraint_type(type_name: str) -> str:
|
|
107
|
+
"""
|
|
108
|
+
@methoddesc 规范化约束类型名称。
|
|
109
|
+
|
|
110
|
+
将各种命名风格的类型名称转换为标准名称(首字母大写)。
|
|
111
|
+
查找顺序:
|
|
112
|
+
1. 直接匹配(标准名称)
|
|
113
|
+
2. 精确匹配别名
|
|
114
|
+
3. 小写匹配别名
|
|
115
|
+
|
|
116
|
+
:param type_name: 约束类型名称(如 "unique"、"Unique"、"not_null" 等)
|
|
117
|
+
:return: 规范化后的标准名称(如 "Unique"、"NotNull" 等)
|
|
118
|
+
"""
|
|
119
|
+
# 步骤1:检查是否是标准名称(已在注册表中)
|
|
120
|
+
if type_name in CONSTRAINT_REGISTRY:
|
|
121
|
+
return type_name
|
|
122
|
+
|
|
123
|
+
# 步骤2:去除首尾空格后精确匹配别名
|
|
124
|
+
key = (type_name or "").strip()
|
|
125
|
+
if key in CONSTRAINT_TYPE_ALIASES:
|
|
126
|
+
return CONSTRAINT_TYPE_ALIASES[key]
|
|
127
|
+
|
|
128
|
+
# 步骤3:小写匹配(支持混合大小写输入)
|
|
129
|
+
lower = key.lower()
|
|
130
|
+
return CONSTRAINT_TYPE_ALIASES.get(lower, key)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def resolve_constraint_class(type_name: str) -> type | None:
|
|
134
|
+
"""@methoddesc 根据标准约束类型名称延迟解析对应的 domain 约束类。
|
|
135
|
+
|
|
136
|
+
通过保存的导入路径字符串动态导入类,避免 core 层在导入时依赖 domain 层。
|
|
137
|
+
|
|
138
|
+
:param type_name: 标准约束类型名称(如 "Unique")
|
|
139
|
+
:return: 对应的约束类;如果未注册则返回 None
|
|
140
|
+
|
|
141
|
+
示例:
|
|
142
|
+
>>> cls = resolve_constraint_class("Unique")
|
|
143
|
+
>>> cls.__name__
|
|
144
|
+
'UniqueConstraint'
|
|
145
|
+
"""
|
|
146
|
+
path = CONSTRAINT_REGISTRY.get(type_name)
|
|
147
|
+
if not path:
|
|
148
|
+
return None
|
|
149
|
+
module_path, class_name = path.rsplit(".", 1)
|
|
150
|
+
module = importlib.import_module(module_path)
|
|
151
|
+
cls = getattr(module, class_name)
|
|
152
|
+
if isinstance(cls, type):
|
|
153
|
+
return cls
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def filter_kwargs_for_class(cls: type[Any], kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
158
|
+
"""
|
|
159
|
+
@methoddesc 按类构造函数签名过滤参数。
|
|
160
|
+
|
|
161
|
+
使用 inspect 模块获取类的 __init__ 方法签名,
|
|
162
|
+
只保留kwargs中与构造函数参数名匹配的键值对。
|
|
163
|
+
这确保了传入的参数不会因为类不接受而导致错误。
|
|
164
|
+
|
|
165
|
+
:param cls: 目标类(通常是约束实现类)
|
|
166
|
+
:param kwargs: 参数字典(可能包含类不接受的键)
|
|
167
|
+
:return: 过滤后的参数字典(只包含类接受的键)
|
|
168
|
+
|
|
169
|
+
示例:
|
|
170
|
+
class MyConstraint:
|
|
171
|
+
def __init__(self, table: str, column: str):
|
|
172
|
+
...
|
|
173
|
+
|
|
174
|
+
kwargs = {"table": "users", "column": "email", "extra": "ignored"}
|
|
175
|
+
result = filter_kwargs_for_class(MyConstraint, kwargs)
|
|
176
|
+
# result = {"table": "users", "column": "email"}
|
|
177
|
+
# "extra" 被过滤掉
|
|
178
|
+
"""
|
|
179
|
+
# 获取类的构造函数签名
|
|
180
|
+
sig = inspect.signature(cls)
|
|
181
|
+
|
|
182
|
+
# 提取构造函数接受的参数名集合
|
|
183
|
+
allowed = set(sig.parameters.keys())
|
|
184
|
+
|
|
185
|
+
# 移除 "self" 参数(不属于构造函数参数)
|
|
186
|
+
allowed.discard("self")
|
|
187
|
+
|
|
188
|
+
# 过滤 kwargs,只保留允许的参数
|
|
189
|
+
# 使用字典推导式进行过滤
|
|
190
|
+
return {k: v for k, v in kwargs.items() if k in allowed}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def get_supported_constraint_types() -> dict[str, str]:
|
|
194
|
+
"""
|
|
195
|
+
@methoddesc 获取当前支持的约束类型及其说明。
|
|
196
|
+
|
|
197
|
+
返回一个字典,键为约束类型名称,值为简要说明。
|
|
198
|
+
供 API 端点和前端使用,用于:
|
|
199
|
+
- 约束类型下拉列表
|
|
200
|
+
- 约束类型文档展示
|
|
201
|
+
- 约束类型发现和验证
|
|
202
|
+
|
|
203
|
+
:return: 约束类型与说明的字典
|
|
204
|
+
|
|
205
|
+
返回值示例:
|
|
206
|
+
{
|
|
207
|
+
"Unique": "唯一约束:支持单列或多列组合",
|
|
208
|
+
"NotNull": "非空约束:列值不能为空",
|
|
209
|
+
...
|
|
210
|
+
}
|
|
211
|
+
"""
|
|
212
|
+
return {
|
|
213
|
+
# 唯一约束:支持单列或多列组合
|
|
214
|
+
"Unique": "唯一约束:支持单列或多列组合",
|
|
215
|
+
# 非空约束:列值不能为空
|
|
216
|
+
"NotNull": "非空约束:列值不能为空",
|
|
217
|
+
# 允许值约束:列值必须来自集合
|
|
218
|
+
"AllowedValues": "允许值约束:列值必须来自集合",
|
|
219
|
+
# 外键约束:参照完整性校验
|
|
220
|
+
"ForeignKey": "外键约束:参照完整性校验",
|
|
221
|
+
# 条件约束:满足 IF 条件时 THEN 必须满足规则
|
|
222
|
+
"Conditional": "条件约束:满足 IF 条件时 THEN 必须满足规则",
|
|
223
|
+
# 脚本约束:逐行执行表达式(需要 allow_unsafe_eval)
|
|
224
|
+
"Scripted": "脚本约束:逐行执行表达式(需要 allow_unsafe_eval)",
|
|
225
|
+
# 字符集约束:校验 ASCII 或中文字符
|
|
226
|
+
"Charset": "字符集约束:校验ASCII或中文字符",
|
|
227
|
+
# 日期逻辑约束:日期比较和计算
|
|
228
|
+
"DateLogic": "日期逻辑约束:日期比较和计算",
|
|
229
|
+
# 区间约束:数值必须在指定范围内
|
|
230
|
+
"Range": "区间约束:数值必须在指定范围内",
|
|
231
|
+
# 组合约束:多个子约束AND/OR组合
|
|
232
|
+
"Composite": "组合约束:多个子约束AND/OR组合",
|
|
233
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
- 统一 ConstraintFile、ConstraintFileV2 和各约束 refs 类型
|
|
22
|
+
- 简化外部模块的导入路径
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- 聚合导出:从 constraint_file.py 和 refs.py 收集类型
|
|
26
|
+
- __all__ 显式控制:明确对外暴露的符号列表
|
|
27
|
+
|
|
28
|
+
输入示例:
|
|
29
|
+
from app.shared.core.project.constraint.types import ConstraintFileV2, UniqueRefs
|
|
30
|
+
|
|
31
|
+
输出示例:
|
|
32
|
+
可直接使用导入的类型构建约束配置
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from .constraint_file import ConstraintFile, ConstraintFileV2, ConstraintType
|
|
36
|
+
from .refs import (
|
|
37
|
+
AllowedValuesRefs,
|
|
38
|
+
CharsetRefs,
|
|
39
|
+
ConditionalIfCondition,
|
|
40
|
+
ConditionalRefs,
|
|
41
|
+
DateLogicRefs,
|
|
42
|
+
ForeignKeyRefs,
|
|
43
|
+
NotNullRefs,
|
|
44
|
+
RangeRefs,
|
|
45
|
+
ScriptedRefs,
|
|
46
|
+
UniqueRefs,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
"ConstraintFile",
|
|
51
|
+
"ConstraintFileV2",
|
|
52
|
+
"ConstraintType",
|
|
53
|
+
"AllowedValuesRefs",
|
|
54
|
+
"CharsetRefs",
|
|
55
|
+
"ConditionalIfCondition",
|
|
56
|
+
"ConditionalRefs",
|
|
57
|
+
"DateLogicRefs",
|
|
58
|
+
"ForeignKeyRefs",
|
|
59
|
+
"NotNullRefs",
|
|
60
|
+
"RangeRefs",
|
|
61
|
+
"ScriptedRefs",
|
|
62
|
+
"UniqueRefs",
|
|
63
|
+
]
|