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,204 @@
|
|
|
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
|
+
- 定义数据类型抽象基类 (DataType)
|
|
21
|
+
- 所有具体数据类型都继承自此基类
|
|
22
|
+
- 提供统一的验证 (validate) 和解析 (parse) 接口
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- 抽象基类: 使用 ABC 定义抽象方法
|
|
26
|
+
- 双重接口: validate() + parse()
|
|
27
|
+
- 子类必须实现两个抽象方法才能实例化
|
|
28
|
+
|
|
29
|
+
输入示例:
|
|
30
|
+
# 子类使用
|
|
31
|
+
class IntegerType(DataType):
|
|
32
|
+
def validate(self, value): ...
|
|
33
|
+
def parse(self, value): ...
|
|
34
|
+
|
|
35
|
+
输出示例:
|
|
36
|
+
# validate(value: Any) -> Tuple[bool, Any]
|
|
37
|
+
# - 验证值是否符合类型要求
|
|
38
|
+
# - 返回: (是否有效, 错误信息)
|
|
39
|
+
# - 成功: (True, None)
|
|
40
|
+
# - 失败: (False, "错误描述")
|
|
41
|
+
|
|
42
|
+
# parse(value: Any) -> Any
|
|
43
|
+
# - 将值转换为目标类型
|
|
44
|
+
# - 返回转换后的值
|
|
45
|
+
|
|
46
|
+
继承体系:
|
|
47
|
+
DataType (抽象基类)
|
|
48
|
+
├── IntegerType # 整数类型
|
|
49
|
+
├── StringType # 字符串类型
|
|
50
|
+
├── FloatType # 浮点数类型
|
|
51
|
+
├── BooleanType # 布尔类型
|
|
52
|
+
├── ExpressionType # 表达式类型
|
|
53
|
+
├── ExtractedType # 提取类型
|
|
54
|
+
├── SequenceType # 序列类型
|
|
55
|
+
└── CompositeConditionType # 复合条件类型
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
from __future__ import annotations
|
|
59
|
+
|
|
60
|
+
# 1. 标准库导入
|
|
61
|
+
from abc import ABC, abstractmethod
|
|
62
|
+
from typing import Any
|
|
63
|
+
|
|
64
|
+
# 2. 第三方库导入
|
|
65
|
+
import pandas as pd
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class DataType(ABC):
|
|
69
|
+
"""
|
|
70
|
+
@classdesc 数据类型抽象基类
|
|
71
|
+
|
|
72
|
+
所有具体数据类型的基类,定义统一的数据类型接口。
|
|
73
|
+
子类必须实现 validate() 和 parse() 方法,否则无法实例化。
|
|
74
|
+
|
|
75
|
+
使用场景:
|
|
76
|
+
- 作为 IntegerType、StringType、FloatType 等具体类型的基类
|
|
77
|
+
- 在 ColumnSchema 中定义列的数据类型
|
|
78
|
+
- 在数据引擎 process_dataframe 中执行类型验证和转换
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
@abstractmethod
|
|
82
|
+
def validate(self, value: Any) -> tuple[bool, Any]:
|
|
83
|
+
"""
|
|
84
|
+
@methoddesc 验证值是否符合类型要求
|
|
85
|
+
|
|
86
|
+
子类必须实现此方法,对单个原始值进行类型合规性检查。
|
|
87
|
+
验证逻辑应专注于格式和语义正确性,不涉及空值判断(空值由调用方处理)。
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
value: 要验证的原始值,可能为字符串、数字、日期对象等任意类型
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
tuple[bool, Any]: 二元组,结构固定为 (is_valid, error_message)
|
|
94
|
+
- is_valid (bool): True 表示值符合该数据类型的格式和语义要求;False 表示验证失败
|
|
95
|
+
- error_message (Any): 验证失败时返回描述性错误信息(通常为 str);验证通过时必须返回 None
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
NotImplementedError: 抽象方法,直接调用基类方法会抛出此异常,强制子类实现
|
|
99
|
+
|
|
100
|
+
示例:
|
|
101
|
+
>>> dtype = IntegerType()
|
|
102
|
+
>>> dtype.validate(123)
|
|
103
|
+
(True, None)
|
|
104
|
+
>>> dtype.validate("abc")
|
|
105
|
+
(False, "'abc' 不是一个严格格式的整数(只允许数字和可选的负号)。")
|
|
106
|
+
"""
|
|
107
|
+
raise NotImplementedError
|
|
108
|
+
|
|
109
|
+
@abstractmethod
|
|
110
|
+
def parse(self, value: Any) -> Any:
|
|
111
|
+
"""
|
|
112
|
+
@methoddesc 将值转换为目标类型
|
|
113
|
+
|
|
114
|
+
子类必须实现此方法,将已通过 validate() 验证的原始值转换为该数据类型的标准 Python 对象。
|
|
115
|
+
调用方应确保在调用 parse() 前先调用 validate() 确认值有效。
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
value: 要转换的原始值,通常为字符串或从数据源读取的原始格式
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Any: 转换后的目标类型值,如 int、float、datetime 对象等,具体类型由子类定义
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
NotImplementedError: 抽象方法,直接调用基类方法会抛出此异常,强制子类实现
|
|
125
|
+
|
|
126
|
+
示例:
|
|
127
|
+
>>> dtype = IntegerType()
|
|
128
|
+
>>> dtype.parse("123")
|
|
129
|
+
123
|
|
130
|
+
"""
|
|
131
|
+
raise NotImplementedError
|
|
132
|
+
|
|
133
|
+
def process_column(self, series: pd.Series, col_name: str, nullable: bool = True) -> tuple[pd.Series, list[dict]]:
|
|
134
|
+
"""
|
|
135
|
+
@methoddesc 对整列数据进行验证和解析(向量化入口)
|
|
136
|
+
|
|
137
|
+
提供逐元素验证 + 解析的默认实现,作为子类覆写前的基线行为。
|
|
138
|
+
处理流程按优先级分为四个分支:非空违反 -> 空值透传 -> 类型验证失败 -> 解析成功。
|
|
139
|
+
子类可覆写此方法以提供更高效的向量化实现(如使用 pandas 的 apply/vectorized 操作)。
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
series: 待处理的 pandas Series,包含从数据源读取的原始列数据
|
|
143
|
+
col_name: 列名,用于构造错误信息中的标识和描述
|
|
144
|
+
nullable: 是否允许空值(None/NaN/空字符串)。False 表示该列不允许为空,遇到空值时记录 NotNullViolation 错误
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
tuple[pd.Series, list[dict]]: 处理结果二元组
|
|
148
|
+
- parsed_series (pd.Series): 解析后的 Series,索引与输入一致;验证失败或空值位置填充为 None
|
|
149
|
+
- errors_list (list[dict]): 错误字典列表,每个字典包含 row_index、column、value、error_type、error_message,格式与 data_engine 保持一致
|
|
150
|
+
"""
|
|
151
|
+
# 收集整列处理过程中发现的所有错误
|
|
152
|
+
errors: list[dict] = []
|
|
153
|
+
# 收集逐元素解析后的值,最终组装为新的 Series
|
|
154
|
+
parsed_values: list[Any] = []
|
|
155
|
+
# 预先计算整列的空值掩码,避免在循环中重复判断,提升性能
|
|
156
|
+
is_na = series.isna()
|
|
157
|
+
|
|
158
|
+
for index, value in series.items():
|
|
159
|
+
# 分支 1:非空约束检查
|
|
160
|
+
# 当 nullable=False 时,拒绝任何空值(pandas NaN 或空字符串)
|
|
161
|
+
if not nullable and (is_na[index] or str(value).strip() == ""):
|
|
162
|
+
errors.append(
|
|
163
|
+
{
|
|
164
|
+
"row_index": index, # 数据行索引,与原始 DataFrame 对齐
|
|
165
|
+
"column": col_name, # 当前处理的列名,用于定位错误位置
|
|
166
|
+
"value": value, # 原始空值,保留用于调试和展示
|
|
167
|
+
"error_type": "NotNullViolation", # 错误类型标识,与前端错误分类对应
|
|
168
|
+
"error_message": f"列 '{col_name}' 不允许为空", # 面向用户的错误描述
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
# 非空违反时,解析结果置为 None,保持 Series 长度与索引一致
|
|
172
|
+
parsed_values.append(None)
|
|
173
|
+
# 进入下一个元素,跳过后续的 validate/parse
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
# 分支 2:空值透传
|
|
177
|
+
# 当列允许为空(nullable=True)且当前值为空时,直接透传 None,不执行类型验证
|
|
178
|
+
if is_na[index]:
|
|
179
|
+
parsed_values.append(None)
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
# 分支 3:类型验证
|
|
183
|
+
# 对非空值调用子类实现的 validate() 进行格式和语义检查
|
|
184
|
+
is_valid, error_message = self.validate(value)
|
|
185
|
+
if not is_valid:
|
|
186
|
+
errors.append(
|
|
187
|
+
{
|
|
188
|
+
"row_index": index,
|
|
189
|
+
"column": col_name,
|
|
190
|
+
"value": value, # 记录原始无效值,便于用户排查数据问题
|
|
191
|
+
"error_type": "TypeValidationError", # 类型验证失败的统一错误标识
|
|
192
|
+
"error_message": error_message, # 由具体子类提供的详细错误描述
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
# 验证失败时,解析结果置为 None,避免将无效数据流入下游处理
|
|
196
|
+
parsed_values.append(None)
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
# 分支 4:解析成功
|
|
200
|
+
# 值已通过 validate() 验证,调用子类实现的 parse() 转换为目标类型
|
|
201
|
+
parsed_values.append(self.parse(value))
|
|
202
|
+
|
|
203
|
+
# 使用原始索引构建新的 Series,确保与输入 DataFrame 的行对齐
|
|
204
|
+
return pd.Series(parsed_values, index=series.index), errors
|
|
@@ -0,0 +1,250 @@
|
|
|
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
|
+
- 支持逻辑组合 (AND/OR) 的条件表达式
|
|
22
|
+
|
|
23
|
+
架构设计:
|
|
24
|
+
- 继承基类: 继承自 DataType 基类
|
|
25
|
+
- 条件解析: 使用正则表达式分割复合条件
|
|
26
|
+
- 注册表匹配: 使用 registry 匹配子条件
|
|
27
|
+
|
|
28
|
+
输入示例:
|
|
29
|
+
# 复合条件表达式
|
|
30
|
+
value = "age > 18 AND status = 'active'"
|
|
31
|
+
|
|
32
|
+
# logical_op = "and" 时,使用 " AND " 分割
|
|
33
|
+
# logical_op = "or" 时,使用 " OR " 分割
|
|
34
|
+
|
|
35
|
+
输出示例:
|
|
36
|
+
# validate() 返回
|
|
37
|
+
(True, None) # 所有子条件都有效
|
|
38
|
+
(False, "复合条件中的子句 'xxx' 不匹配任何已知的模式。")
|
|
39
|
+
|
|
40
|
+
# parse() 返回解析后的条件列表
|
|
41
|
+
[
|
|
42
|
+
{"field": "age", "operator": ">", "value": 18},
|
|
43
|
+
{"field": "status", "operator": "=", "value": "active"}
|
|
44
|
+
]
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
from __future__ import annotations
|
|
48
|
+
|
|
49
|
+
# 1. 标准库导入
|
|
50
|
+
import re
|
|
51
|
+
from typing import Any
|
|
52
|
+
|
|
53
|
+
# 2. 项目内部导入
|
|
54
|
+
from app.shared.domain.data_types_parts.base import DataType
|
|
55
|
+
from app.shared.domain.expression_system import ExpressionRegistry
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class CompositeConditionType(DataType):
|
|
59
|
+
"""
|
|
60
|
+
@classdesc 复合条件类型
|
|
61
|
+
|
|
62
|
+
支持逻辑组合(AND/OR)的条件表达式类型。
|
|
63
|
+
将复合条件字符串按逻辑运算符分割为原子条件,逐个验证。
|
|
64
|
+
|
|
65
|
+
使用场景:
|
|
66
|
+
- 验证包含多个子条件的复合条件表达式
|
|
67
|
+
- 如 "age > 18 AND status = 'active'"
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, registry: ExpressionRegistry, logical_op: str = "and") -> None:
|
|
71
|
+
"""
|
|
72
|
+
@methoddesc 初始化复合条件类型
|
|
73
|
+
|
|
74
|
+
参数:
|
|
75
|
+
registry: 表达式注册表,用于匹配原子条件
|
|
76
|
+
logical_op: 逻辑运算符,"and" 或 "or"
|
|
77
|
+
"""
|
|
78
|
+
self.registry = registry
|
|
79
|
+
self.splitter = re.compile(r"\s+" + re.escape(logical_op) + r"\s+")
|
|
80
|
+
|
|
81
|
+
def validate(self, value: Any) -> tuple[bool, Any]:
|
|
82
|
+
"""
|
|
83
|
+
@methoddesc 验证复合条件表达式
|
|
84
|
+
|
|
85
|
+
将表达式按逻辑运算符分割为原子条件,逐个使用注册表匹配验证。
|
|
86
|
+
|
|
87
|
+
参数:
|
|
88
|
+
value: 条件表达式字符串
|
|
89
|
+
|
|
90
|
+
返回:
|
|
91
|
+
元组 (is_valid, error_message)
|
|
92
|
+
"""
|
|
93
|
+
if not isinstance(value, str):
|
|
94
|
+
return False, f"期望是字符串, 但得到了 {type(value).__name__}。"
|
|
95
|
+
if not value.strip():
|
|
96
|
+
return True, None
|
|
97
|
+
atomic_conditions = self.splitter.split(value)
|
|
98
|
+
for i, atomic_str in enumerate(atomic_conditions):
|
|
99
|
+
atomic_str = atomic_str.strip()
|
|
100
|
+
if not atomic_str:
|
|
101
|
+
return False, f"在第 {i} 个 '{self.splitter.pattern}' 分割符附近发现了空的条件表达式。"
|
|
102
|
+
match_result = self.registry.find_match(atomic_str)
|
|
103
|
+
if not match_result:
|
|
104
|
+
return False, f"复合条件中的子句 '{atomic_str}' 不匹配任何已知的模式。"
|
|
105
|
+
pattern, match = match_result
|
|
106
|
+
try:
|
|
107
|
+
pattern.parser_func(match.groupdict())
|
|
108
|
+
except (ValueError, KeyError) as e:
|
|
109
|
+
return False, f"子句 '{atomic_str}' 验证失败: {e}"
|
|
110
|
+
return True, None
|
|
111
|
+
|
|
112
|
+
def parse(self, value: Any) -> list[dict[str, Any]]:
|
|
113
|
+
"""
|
|
114
|
+
@methoddesc 解析复合条件表达式
|
|
115
|
+
|
|
116
|
+
将表达式按逻辑运算符分割为原子条件,逐个解析为结构化数据。
|
|
117
|
+
|
|
118
|
+
参数:
|
|
119
|
+
value: 条件表达式字符串
|
|
120
|
+
|
|
121
|
+
返回:
|
|
122
|
+
解析后的条件列表,每个元素是包含 type 和 value 的字典
|
|
123
|
+
"""
|
|
124
|
+
if not isinstance(value, str) or not value.strip():
|
|
125
|
+
return []
|
|
126
|
+
parsed_conditions = []
|
|
127
|
+
atomic_conditions = self.splitter.split(value)
|
|
128
|
+
for atomic_str in atomic_conditions:
|
|
129
|
+
atomic_str = atomic_str.strip()
|
|
130
|
+
if not atomic_str:
|
|
131
|
+
continue
|
|
132
|
+
find_result = self.registry.find_match(atomic_str)
|
|
133
|
+
if find_result is None:
|
|
134
|
+
raise ValueError(f"子条件 '{atomic_str}' 不匹配任何已注册的表达式模式")
|
|
135
|
+
pattern, match = find_result
|
|
136
|
+
parsed_value = pattern.parser_func(match.groupdict())
|
|
137
|
+
parsed_conditions.append({"type": pattern.name, "value": parsed_value})
|
|
138
|
+
return parsed_conditions
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class SpecificCompositeConditionType(DataType):
|
|
142
|
+
"""
|
|
143
|
+
@classdesc 特定复合条件类型
|
|
144
|
+
|
|
145
|
+
针对单一模式的精确复合条件类型。
|
|
146
|
+
初始化时指定具体模式名称,验证时仅匹配该模式。
|
|
147
|
+
|
|
148
|
+
使用场景:
|
|
149
|
+
- 需要限制复合条件中只能使用特定模式时
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
def __init__(self, registry: ExpressionRegistry, pattern: str, logical_op: str = "and") -> None:
|
|
153
|
+
"""
|
|
154
|
+
@methoddesc 初始化特定复合条件类型
|
|
155
|
+
|
|
156
|
+
参数:
|
|
157
|
+
registry: 表达式注册表
|
|
158
|
+
pattern: 指定的模式名称
|
|
159
|
+
logical_op: 逻辑运算符,"and" 或 "or"
|
|
160
|
+
|
|
161
|
+
抛出:
|
|
162
|
+
ValueError: 当注册表中未找到指定模式时抛出
|
|
163
|
+
"""
|
|
164
|
+
self.registry = registry
|
|
165
|
+
self.pattern_name = pattern
|
|
166
|
+
self.specific_pattern = None
|
|
167
|
+
for p in registry._patterns:
|
|
168
|
+
if p.name == pattern:
|
|
169
|
+
self.specific_pattern = p
|
|
170
|
+
break
|
|
171
|
+
if self.specific_pattern is None:
|
|
172
|
+
raise ValueError(f"在注册表中未找到指定的模式: '{pattern}'")
|
|
173
|
+
self.splitter = re.compile(r"\s+" + re.escape(logical_op) + r"\s+")
|
|
174
|
+
|
|
175
|
+
def validate(self, value: Any) -> tuple[bool, Any]:
|
|
176
|
+
"""
|
|
177
|
+
@methoddesc 验证特定复合条件表达式
|
|
178
|
+
|
|
179
|
+
与通用 CompositeConditionType 不同,本方法不仅要求子句能被注册表匹配,
|
|
180
|
+
还要求匹配到的模式与初始化时指定的 specific_pattern 一致。
|
|
181
|
+
过去本方法忽略了 specific_pattern,导致限定模式后仍接受其他子句。
|
|
182
|
+
|
|
183
|
+
参数:
|
|
184
|
+
value: 条件表达式字符串
|
|
185
|
+
|
|
186
|
+
返回:
|
|
187
|
+
元组 (is_valid, error_message)
|
|
188
|
+
"""
|
|
189
|
+
specific = self.specific_pattern
|
|
190
|
+
if specific is None:
|
|
191
|
+
return False, "内部错误:specific_pattern 未初始化"
|
|
192
|
+
if not isinstance(value, str):
|
|
193
|
+
return False, f"期望是字符串, 但得到了 {type(value).__name__}。"
|
|
194
|
+
if not value.strip():
|
|
195
|
+
return True, None
|
|
196
|
+
atomic_conditions = self.splitter.split(value)
|
|
197
|
+
for i, atomic_str in enumerate(atomic_conditions):
|
|
198
|
+
atomic_str = atomic_str.strip()
|
|
199
|
+
if not atomic_str:
|
|
200
|
+
return False, f"在第 {i} 个 '{self.splitter.pattern}' 分割符附近发现了空的条件表达式。"
|
|
201
|
+
match_result = self.registry.find_match(atomic_str)
|
|
202
|
+
if not match_result:
|
|
203
|
+
return False, f"复合条件中的子句 '{atomic_str}' 不匹配任何已知的模式。"
|
|
204
|
+
pattern, match = match_result
|
|
205
|
+
# 校验匹配到的模式必须与 specific_pattern 一致
|
|
206
|
+
if pattern is None or pattern.name != specific.name:
|
|
207
|
+
return (
|
|
208
|
+
False,
|
|
209
|
+
f"子句 '{atomic_str}' 匹配的模式 '{getattr(pattern, 'name', '?')}' 与限定模式 "
|
|
210
|
+
f"'{specific.name}' 不一致,本类型只允许使用限定模式。",
|
|
211
|
+
)
|
|
212
|
+
try:
|
|
213
|
+
pattern.parser_func(match.groupdict())
|
|
214
|
+
except (ValueError, KeyError) as e:
|
|
215
|
+
return False, f"子句 '{atomic_str}' 验证失败: {e}"
|
|
216
|
+
return True, None
|
|
217
|
+
|
|
218
|
+
def parse(self, value: Any) -> list[dict[str, Any]]:
|
|
219
|
+
"""
|
|
220
|
+
@methoddesc 解析特定复合条件表达式
|
|
221
|
+
|
|
222
|
+
仅解析与 specific_pattern 匹配的子句,忽略其他模式(与 validate 语义一致)。
|
|
223
|
+
|
|
224
|
+
参数:
|
|
225
|
+
value: 条件表达式字符串
|
|
226
|
+
|
|
227
|
+
返回:
|
|
228
|
+
解析后的条件列表,每个元素是包含 type 和 value 的字典
|
|
229
|
+
"""
|
|
230
|
+
if not isinstance(value, str) or not value.strip():
|
|
231
|
+
return []
|
|
232
|
+
specific = self.specific_pattern
|
|
233
|
+
if specific is None:
|
|
234
|
+
return []
|
|
235
|
+
parsed_conditions = []
|
|
236
|
+
atomic_conditions = self.splitter.split(value)
|
|
237
|
+
for atomic_str in atomic_conditions:
|
|
238
|
+
atomic_str = atomic_str.strip()
|
|
239
|
+
if not atomic_str:
|
|
240
|
+
continue
|
|
241
|
+
match_result = self.registry.find_match(atomic_str)
|
|
242
|
+
if not match_result:
|
|
243
|
+
continue
|
|
244
|
+
pattern, match = match_result
|
|
245
|
+
# 仅解析与 specific_pattern 一致的子句
|
|
246
|
+
if pattern is None or pattern.name != specific.name:
|
|
247
|
+
continue
|
|
248
|
+
parsed_value = pattern.parser_func(match.groupdict())
|
|
249
|
+
parsed_conditions.append({"type": pattern.name, "value": parsed_value})
|
|
250
|
+
return parsed_conditions
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
r"""
|
|
17
|
+
@fileoverview 表达式数据类型模块
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 定义 ExpressionType 支持从注册表匹配和验证表达式格式
|
|
21
|
+
- 定义 SpecificExpressionType 针对单一模式的精确匹配
|
|
22
|
+
- 提供 validate() 和 parse() 方法用于表达式校验与解析
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- 继承基类: 继承自 DataType 基类,遵循统一的数据类型接口
|
|
26
|
+
- 注册表驱动: 使用 ExpressionRegistry 匹配表达式模式
|
|
27
|
+
- 解析委托: 将具体解析逻辑委托给 pattern.parser_func
|
|
28
|
+
|
|
29
|
+
输入示例:
|
|
30
|
+
# 注册表中的表达式模式
|
|
31
|
+
registry = ExpressionRegistry(patterns=[
|
|
32
|
+
Pattern(name="add", pattern=r"add\({a},{b}\)", parser_func=...)
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
# 验证表达式
|
|
36
|
+
ExpressionType(registry).validate("add(1,2)") # (True, None)
|
|
37
|
+
ExpressionType(registry).validate("unknown(1,2)") # (False, "不匹配任何已注册的表达式格式")
|
|
38
|
+
|
|
39
|
+
输出示例:
|
|
40
|
+
# validate() 返回
|
|
41
|
+
(True, None) # 表达式有效
|
|
42
|
+
(False, "表达式 'xxx' 格式正确但内容无效: ...") # 表达式格式错
|
|
43
|
+
|
|
44
|
+
# parse() 返回
|
|
45
|
+
{
|
|
46
|
+
"type": "add",
|
|
47
|
+
"value": {"a": 1, "b": 2}
|
|
48
|
+
}
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
from __future__ import annotations
|
|
52
|
+
|
|
53
|
+
# 1. 标准库导入
|
|
54
|
+
from typing import Any
|
|
55
|
+
|
|
56
|
+
# 2. 项目内部导入
|
|
57
|
+
from app.shared.domain.data_types_parts.base import DataType
|
|
58
|
+
from app.shared.domain.expression_system import ExpressionPattern, ExpressionRegistry
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ExpressionType(DataType):
|
|
62
|
+
"""
|
|
63
|
+
@classdesc 表达式类型
|
|
64
|
+
|
|
65
|
+
支持从注册表匹配和验证表达式格式的数据类型。
|
|
66
|
+
使用 ExpressionRegistry 查找匹配的表达式模式,常用于验证手机号、邮箱等格式。
|
|
67
|
+
|
|
68
|
+
使用场景:
|
|
69
|
+
- 验证数据是否符合预定义的表达式模式
|
|
70
|
+
- 将符合模式的字符串解析为结构化数据
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
name = "Expr"
|
|
74
|
+
|
|
75
|
+
def __init__(self, registry: ExpressionRegistry) -> None:
|
|
76
|
+
"""
|
|
77
|
+
@methoddesc 初始化表达式类型
|
|
78
|
+
|
|
79
|
+
参数:
|
|
80
|
+
registry: 表达式注册表,包含所有可用的表达式模式
|
|
81
|
+
"""
|
|
82
|
+
self.registry = registry
|
|
83
|
+
|
|
84
|
+
def validate(self, value: Any) -> tuple[bool, Any]:
|
|
85
|
+
"""
|
|
86
|
+
@methoddesc 验证表达式字符串
|
|
87
|
+
|
|
88
|
+
在注册表中查找匹配的模式,并调用模式的解析函数验证内容。
|
|
89
|
+
|
|
90
|
+
参数:
|
|
91
|
+
value: 要验证的字符串
|
|
92
|
+
|
|
93
|
+
返回:
|
|
94
|
+
元组 (is_valid, error_message)
|
|
95
|
+
"""
|
|
96
|
+
if not isinstance(value, str):
|
|
97
|
+
return False, f"期望是字符串, 但得到了 {type(value).__name__}。"
|
|
98
|
+
match_result = self.registry.find_match(value)
|
|
99
|
+
if not match_result:
|
|
100
|
+
return False, f"'{value}' 不匹配任何已注册的表达式格式。"
|
|
101
|
+
pattern, match = match_result
|
|
102
|
+
try:
|
|
103
|
+
pattern.parser_func(match.groupdict())
|
|
104
|
+
return True, None
|
|
105
|
+
except (ValueError, KeyError) as e:
|
|
106
|
+
return False, f"表达式 '{value}' 格式正确但内容无效: {e}"
|
|
107
|
+
|
|
108
|
+
def parse(self, value: Any) -> dict[str, Any]:
|
|
109
|
+
"""
|
|
110
|
+
@methoddesc 解析表达式字符串
|
|
111
|
+
|
|
112
|
+
在注册表中查找匹配的模式,并调用解析函数生成结构化数据。
|
|
113
|
+
|
|
114
|
+
参数:
|
|
115
|
+
value: 要解析的字符串
|
|
116
|
+
|
|
117
|
+
返回:
|
|
118
|
+
包含 type 和 value 的字典
|
|
119
|
+
"""
|
|
120
|
+
find_result = self.registry.find_match(value)
|
|
121
|
+
if find_result is None:
|
|
122
|
+
raise ValueError(f"值 '{value}' 不匹配任何已注册的表达式模式")
|
|
123
|
+
pattern, match = find_result
|
|
124
|
+
return {"type": pattern.name, "value": pattern.parser_func(match.groupdict())}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class SpecificExpressionType(DataType):
|
|
128
|
+
"""
|
|
129
|
+
@classdesc 特定表达式类型
|
|
130
|
+
|
|
131
|
+
针对单一模式的精确表达式类型。
|
|
132
|
+
初始化时指定具体模式名称,验证时仅匹配该模式。
|
|
133
|
+
|
|
134
|
+
使用场景:
|
|
135
|
+
- 需要限制列只能使用特定表达式模式时
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def __init__(self, registry: ExpressionRegistry, pattern: str) -> None:
|
|
139
|
+
"""
|
|
140
|
+
@methoddesc 初始化特定表达式类型
|
|
141
|
+
|
|
142
|
+
参数:
|
|
143
|
+
registry: 表达式注册表
|
|
144
|
+
pattern: 指定的模式名称
|
|
145
|
+
|
|
146
|
+
抛出:
|
|
147
|
+
ValueError: 当注册表中未找到指定模式时抛出
|
|
148
|
+
"""
|
|
149
|
+
self.registry = registry
|
|
150
|
+
self.pattern_name = pattern
|
|
151
|
+
specific_pattern: ExpressionPattern | None = None
|
|
152
|
+
for p in registry._patterns:
|
|
153
|
+
if p.name == pattern:
|
|
154
|
+
specific_pattern = p
|
|
155
|
+
break
|
|
156
|
+
if specific_pattern is None:
|
|
157
|
+
raise ValueError(f"在注册表中未找到指定的模式: '{pattern}'")
|
|
158
|
+
if isinstance(specific_pattern.regex, str):
|
|
159
|
+
import re
|
|
160
|
+
|
|
161
|
+
specific_pattern.regex = re.compile(specific_pattern.regex)
|
|
162
|
+
self.specific_pattern = specific_pattern
|
|
163
|
+
|
|
164
|
+
def validate(self, value: Any) -> tuple[bool, Any]:
|
|
165
|
+
"""
|
|
166
|
+
@methoddesc 验证特定表达式字符串
|
|
167
|
+
|
|
168
|
+
使用指定的模式进行精确匹配。
|
|
169
|
+
|
|
170
|
+
参数:
|
|
171
|
+
value: 要验证的字符串
|
|
172
|
+
|
|
173
|
+
返回:
|
|
174
|
+
元组 (is_valid, error_message)
|
|
175
|
+
"""
|
|
176
|
+
if not isinstance(value, str):
|
|
177
|
+
return False, f"期望是字符串, 但得到了 {type(value).__name__}。"
|
|
178
|
+
match = self.specific_pattern.regex.fullmatch(value.strip())
|
|
179
|
+
if not match:
|
|
180
|
+
return False, f"'{value}' 不匹配指定的表达式格式 '{self.pattern_name}'。"
|
|
181
|
+
try:
|
|
182
|
+
self.specific_pattern.parser_func(match.groupdict())
|
|
183
|
+
return True, None
|
|
184
|
+
except (ValueError, KeyError) as e:
|
|
185
|
+
return False, f"表达式 '{value}' 格式正确但内容无效: {e}"
|
|
186
|
+
|
|
187
|
+
def parse(self, value: Any) -> dict[str, Any]:
|
|
188
|
+
"""
|
|
189
|
+
@methoddesc 解析特定表达式字符串
|
|
190
|
+
|
|
191
|
+
使用指定的模式进行精确匹配并解析。
|
|
192
|
+
|
|
193
|
+
参数:
|
|
194
|
+
value: 要解析的字符串
|
|
195
|
+
|
|
196
|
+
返回:
|
|
197
|
+
包含 type 和 value 的字典
|
|
198
|
+
"""
|
|
199
|
+
match = self.specific_pattern.regex.fullmatch(value.strip())
|
|
200
|
+
if not match:
|
|
201
|
+
return {"type": self.specific_pattern.name, "value": None}
|
|
202
|
+
return {"type": self.specific_pattern.name, "value": self.specific_pattern.parser_func(match.groupdict())}
|