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,129 @@
|
|
|
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
|
+
- 定义序列类型 (SequenceType)
|
|
21
|
+
- 支持用分隔符分隔的多个值列表
|
|
22
|
+
- 每个元素使用指定的数据类型进行验证和解析
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- 继承基类: 继承自 DataType 基类
|
|
26
|
+
- 元素验证: 使用 item_type 验证每个元素
|
|
27
|
+
- 分隔符: 可配置的 delimiter (默认 ";")
|
|
28
|
+
|
|
29
|
+
输入示例:
|
|
30
|
+
# 整数序列,用分号分隔
|
|
31
|
+
sequence = SequenceType(item_type=IntegerType(), delimiter=";")
|
|
32
|
+
|
|
33
|
+
# 验证序列
|
|
34
|
+
sequence.validate("1;2;3;4") # (True, None)
|
|
35
|
+
sequence.validate("1;abc;3") # (False, "第2个元素验证失败")
|
|
36
|
+
sequence.validate("1;;3") # (True, None) - 空元素跳过
|
|
37
|
+
|
|
38
|
+
输出示例:
|
|
39
|
+
# validate() 返回
|
|
40
|
+
(True, None) # 所有元素都有效
|
|
41
|
+
(False, "序列中的第 2 个元素 'abc' 验证失败: 不是一个整数")
|
|
42
|
+
|
|
43
|
+
# parse() 返回解析后的列表
|
|
44
|
+
SequenceType(IntegerType()).parse("1;2;3") # [1, 2, 3]
|
|
45
|
+
SequenceType(StringType()).parse("a;b;c") # ["a", "b", "c"]
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
from __future__ import annotations
|
|
49
|
+
|
|
50
|
+
# 1. 标准库导入
|
|
51
|
+
from typing import Any
|
|
52
|
+
|
|
53
|
+
# 2. 项目内部导入
|
|
54
|
+
from app.shared.domain.data_types_parts.base import DataType
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class SequenceType(DataType):
|
|
58
|
+
"""
|
|
59
|
+
@classdesc 序列类型
|
|
60
|
+
|
|
61
|
+
支持用分隔符分隔的多个值列表。
|
|
62
|
+
每个元素使用 item_type 进行验证和解析。
|
|
63
|
+
|
|
64
|
+
使用场景:
|
|
65
|
+
- 标签列表: "tag1;tag2;tag3"
|
|
66
|
+
- ID 列表: "1;2;3;4"
|
|
67
|
+
- 多值字段的校验和解析
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, item_type: DataType, delimiter: str = ";"):
|
|
71
|
+
"""
|
|
72
|
+
@methoddesc 初始化序列类型
|
|
73
|
+
|
|
74
|
+
参数:
|
|
75
|
+
item_type: 元素的数据类型,用于验证和解析每个元素
|
|
76
|
+
delimiter: 分隔符,默认 ";"
|
|
77
|
+
"""
|
|
78
|
+
self.item_type = item_type
|
|
79
|
+
self.delimiter = delimiter
|
|
80
|
+
|
|
81
|
+
def validate(self, value: Any) -> tuple[bool, Any]:
|
|
82
|
+
"""
|
|
83
|
+
@methoddesc 验证序列字符串
|
|
84
|
+
|
|
85
|
+
将字符串按分隔符分割,逐个验证每个元素。
|
|
86
|
+
空元素会被跳过,不视为错误。
|
|
87
|
+
|
|
88
|
+
参数:
|
|
89
|
+
value: 要验证的序列字符串
|
|
90
|
+
|
|
91
|
+
返回:
|
|
92
|
+
元组 (is_valid, error_message)
|
|
93
|
+
"""
|
|
94
|
+
if not isinstance(value, str):
|
|
95
|
+
return False, f"期望是字符串,但得到了 {type(value).__name__}。"
|
|
96
|
+
if not value.strip():
|
|
97
|
+
return True, None
|
|
98
|
+
parts = value.split(self.delimiter)
|
|
99
|
+
for i, part in enumerate(parts):
|
|
100
|
+
part = part.strip()
|
|
101
|
+
if not part:
|
|
102
|
+
continue
|
|
103
|
+
is_valid, error_message = self.item_type.validate(part)
|
|
104
|
+
if not is_valid:
|
|
105
|
+
return False, f"序列中的第 {i + 1} 个元素 '{part}' 验证失败: {error_message}"
|
|
106
|
+
return True, None
|
|
107
|
+
|
|
108
|
+
def parse(self, value: Any) -> list:
|
|
109
|
+
"""
|
|
110
|
+
@methoddesc 解析序列字符串
|
|
111
|
+
|
|
112
|
+
将字符串按分隔符分割,逐个解析每个元素。
|
|
113
|
+
空元素会被跳过,不包含在结果中。
|
|
114
|
+
|
|
115
|
+
参数:
|
|
116
|
+
value: 要解析的序列字符串
|
|
117
|
+
|
|
118
|
+
返回:
|
|
119
|
+
解析后的元素列表
|
|
120
|
+
"""
|
|
121
|
+
if not isinstance(value, str) or not value.strip():
|
|
122
|
+
return []
|
|
123
|
+
parsed_list = []
|
|
124
|
+
parts = value.split(self.delimiter)
|
|
125
|
+
for part in parts:
|
|
126
|
+
part = part.strip()
|
|
127
|
+
if part:
|
|
128
|
+
parsed_list.append(self.item_type.parse(part))
|
|
129
|
+
return parsed_list
|
|
@@ -0,0 +1,48 @@
|
|
|
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 数据 Schema 统一导出入口模块
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 作为 dataset_schema 包的统一导出入口
|
|
21
|
+
- 聚合导出 schema/builder.py 中的类型构建器和注册表
|
|
22
|
+
- 聚合导出 schema/models.py 中的 Schema 模型类
|
|
23
|
+
- 降低外部调用方的导入成本,只需从一个入口导入所有 Schema 相关符号
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- 代理导出模式: 实际实现已拆分为 builder.py 和 models.py
|
|
27
|
+
- 本模块仅做 re-export,无独立业务逻辑
|
|
28
|
+
- 保持向后兼容,避免导入路径变更
|
|
29
|
+
|
|
30
|
+
注意: CONSTRAINT_REGISTRY 已迁移至 app.shared.core.project.constraint.registry
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# 1. 项目内部导入
|
|
34
|
+
from .schema.builder import (
|
|
35
|
+
TYPE_REGISTRY,
|
|
36
|
+
build_type_from_config,
|
|
37
|
+
)
|
|
38
|
+
from .schema.models import ColumnSchema, DataSetSchema, TableSchema
|
|
39
|
+
|
|
40
|
+
# 注:models.py 也 re-export TYPE_REGISTRY 以兼容旧导入路径,单一事实源在 builder.py。
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"ColumnSchema",
|
|
44
|
+
"TableSchema",
|
|
45
|
+
"DataSetSchema",
|
|
46
|
+
"build_type_from_config",
|
|
47
|
+
"TYPE_REGISTRY",
|
|
48
|
+
]
|
|
@@ -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
|
+
"""@fileoverview 表达式求值沙箱(共享)
|
|
17
|
+
|
|
18
|
+
功能概述:
|
|
19
|
+
- 提供 simpleeval 沙箱的统一配置(安全函数白名单),供 Scripted 约束与 MathExpr 转换共用。
|
|
20
|
+
- B-sec5: 消除 df.eval 的表达式注入面,统一改用 simpleeval 逐行求值。
|
|
21
|
+
|
|
22
|
+
设计原则:
|
|
23
|
+
- 只暴露确定安全的基础函数,禁止属性访问逃逸。
|
|
24
|
+
- names(变量绑定)由调用方按场景注入(约束注入 row/value,转换注入列值)。
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from simpleeval import SimpleEval
|
|
30
|
+
|
|
31
|
+
# 安全函数白名单:仅暴露纯函数,不含任何可访问属性/模块的对象。
|
|
32
|
+
# B-sec5: simpleeval 默认禁用属性访问与 dunder,配合此白名单可杜绝 __import__/open 等逃逸。
|
|
33
|
+
SAFE_FUNCTIONS: dict[str, object] = {
|
|
34
|
+
"len": len,
|
|
35
|
+
"sum": sum,
|
|
36
|
+
"max": max,
|
|
37
|
+
"min": min,
|
|
38
|
+
"round": round,
|
|
39
|
+
"abs": abs,
|
|
40
|
+
"any": any,
|
|
41
|
+
"all": all,
|
|
42
|
+
"int": int,
|
|
43
|
+
"str": str,
|
|
44
|
+
"float": float,
|
|
45
|
+
"bool": bool,
|
|
46
|
+
"list": list,
|
|
47
|
+
"dict": dict,
|
|
48
|
+
"set": set,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def make_sandbox_evaluator(names: dict[str, object] | None = None) -> SimpleEval:
|
|
53
|
+
"""构造一个配置好安全白名单的 SimpleEval 求值器。
|
|
54
|
+
|
|
55
|
+
参数:
|
|
56
|
+
names: 变量名绑定(如约束场景的 row/value,转换场景的列名→列值)。
|
|
57
|
+
|
|
58
|
+
返回:
|
|
59
|
+
SimpleEval 实例,调用 .eval(expression) 求值。
|
|
60
|
+
"""
|
|
61
|
+
evaluator = SimpleEval(names=names or {})
|
|
62
|
+
evaluator.functions.update(SAFE_FUNCTIONS)
|
|
63
|
+
return evaluator
|
|
@@ -0,0 +1,366 @@
|
|
|
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
|
+
- 支持类型转换器字典(TYPE_CASTERS)实现字符串到目标类型的转换
|
|
22
|
+
- 提供模板解析器工厂函数和表达式注册表
|
|
23
|
+
- 主要应用于数据验证场景
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- TYPE_CASTERS: 类型转换器字典,将字符串转换为目标类型
|
|
27
|
+
- create_templated_parser: 创建模板解析器工厂函数
|
|
28
|
+
- ExpressionPattern: 表达式模式数据类
|
|
29
|
+
- ExpressionRegistry: 表达式注册表
|
|
30
|
+
|
|
31
|
+
输入示例:
|
|
32
|
+
template = {"type": "phone_cn", "value": "{value:int}"}
|
|
33
|
+
parser = create_templated_parser(template)
|
|
34
|
+
result = parser({"value": "13800138000"})
|
|
35
|
+
|
|
36
|
+
输出示例:
|
|
37
|
+
{"type": "phone_cn", "value": 13800138000}
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# 1. 标准库导入
|
|
41
|
+
import re
|
|
42
|
+
from collections.abc import Callable
|
|
43
|
+
from dataclasses import dataclass
|
|
44
|
+
from datetime import datetime
|
|
45
|
+
from typing import Any
|
|
46
|
+
|
|
47
|
+
# ============================================================================
|
|
48
|
+
# 类型转换器
|
|
49
|
+
# ============================================================================
|
|
50
|
+
# 将字符串转换为目标类型
|
|
51
|
+
# 使用示例:
|
|
52
|
+
# "" -> str (默认字符串)
|
|
53
|
+
# "int" -> int (整数)
|
|
54
|
+
# "float" -> float (浮点数)
|
|
55
|
+
# "date" -> date (日期格式 YYYYMMDD)
|
|
56
|
+
# "bool" -> bool (布尔值)
|
|
57
|
+
|
|
58
|
+
# 类型转换器字典:键为类型名称,值为对应的转换函数
|
|
59
|
+
# 在模板解析时,根据 {name:type} 中的 type 查找对应的转换器,将字符串捕获组转换为目标类型
|
|
60
|
+
TYPE_CASTERS: dict[str, Callable[[str], Any]] = {
|
|
61
|
+
"": str, # 默认类型:保持字符串原样
|
|
62
|
+
"str": str, # 字符串类型:显式声明,效果同默认
|
|
63
|
+
"int": int, # 整数类型:调用 int() 转换
|
|
64
|
+
"float": float, # 浮点数类型:调用 float() 转换
|
|
65
|
+
"date": lambda s: datetime.strptime(s, "%Y%m%d").date(), # 日期类型:按 YYYYMMDD 格式解析
|
|
66
|
+
"bool": lambda s: s.lower() in ["true", "1", "t", "y", "yes"], # 布尔类型:支持多种真值表示
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def create_templated_parser(output_template: dict[str, Any]) -> Callable[[dict[str, str]], dict[str, Any]]:
|
|
71
|
+
"""
|
|
72
|
+
创建模板解析器。
|
|
73
|
+
|
|
74
|
+
============================================================================
|
|
75
|
+
输入数据示例 (这个函数接收什么数据)
|
|
76
|
+
============================================================================
|
|
77
|
+
本函数接收一个输出模板字典:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
# 简单模板
|
|
81
|
+
template1 = {
|
|
82
|
+
"type": "phone_cn",
|
|
83
|
+
"prefix": "+86"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# 动态模板(带类型转换)
|
|
87
|
+
template2 = {
|
|
88
|
+
"type": "phone_cn",
|
|
89
|
+
"value": "{value:int}", # 将捕获组转换为整数
|
|
90
|
+
"area_code": "{area:str}" # 将捕获组转换为字符串
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# 复杂模板
|
|
94
|
+
template3 = {
|
|
95
|
+
"type": "email",
|
|
96
|
+
"username": "{username:str}",
|
|
97
|
+
"domain": "{domain:str}",
|
|
98
|
+
"is_business": "{isBiz:bool}"
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
============================================================================
|
|
103
|
+
业务场景 (什么情况下会调用这个函数)
|
|
104
|
+
============================================================================
|
|
105
|
+
- 场景1: 定义表达式解析规则
|
|
106
|
+
当需要将符合特定格式的字符串解析为结构化数据时使用。
|
|
107
|
+
|
|
108
|
+
- 场景2: 数据验证
|
|
109
|
+
验证用户输入的数据格式,并转换为正确的类型。
|
|
110
|
+
|
|
111
|
+
============================================================================
|
|
112
|
+
数据流 (输入如何变成输出)
|
|
113
|
+
============================================================================
|
|
114
|
+
输入参数:
|
|
115
|
+
- output_template: Dict[str, Any],输出模板字典
|
|
116
|
+
示例值: {"type": "phone", "value": "{value:int}"}
|
|
117
|
+
|
|
118
|
+
处理步骤:
|
|
119
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
120
|
+
│ Step 1: 解析模板 │
|
|
121
|
+
├─────────────────────────────────────────────────────────────┤
|
|
122
|
+
│ 输入: {"value": "{value:int}"} │
|
|
123
|
+
│ 操作: 正则匹配 {name:type} 格式 │
|
|
124
|
+
│ 输出: [(value, dynamic, (value, int)), ...] │
|
|
125
|
+
└─────────────────────────────────────────────────────────────┘
|
|
126
|
+
|
|
127
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
128
|
+
│ Step 2: 创建解析器函数 │
|
|
129
|
+
├─────────────────────────────────────────────────────────────┤
|
|
130
|
+
│ 输入: 解析后的模板规范 │
|
|
131
|
+
│ 操作: 返回一个闭包函数 │
|
|
132
|
+
│ 输出: parser 函数 │
|
|
133
|
+
└─────────────────────────────────────────────────────────────┘
|
|
134
|
+
|
|
135
|
+
最终输出: Callable[[Dict[str, str]], Dict[str, Any]]
|
|
136
|
+
示例:
|
|
137
|
+
parser = create_templated_parser({"value": "{value:int}"})
|
|
138
|
+
result = parser({"value": "123"})
|
|
139
|
+
# 结果: {"value": 123}
|
|
140
|
+
|
|
141
|
+
============================================================================
|
|
142
|
+
类型转换格式
|
|
143
|
+
============================================================================
|
|
144
|
+
| 格式 | 说明 | 示例 |
|
|
145
|
+
|------|------|------|
|
|
146
|
+
| {name:} | 字符串(默认)| {value:} -> "123" |
|
|
147
|
+
| {name:str} | 字符串 | {value:str} -> "123" |
|
|
148
|
+
| {name:int} | 整数 | {value:int} -> 123 |
|
|
149
|
+
| {name:float} | 浮点数 | {value:float} -> 123.45 |
|
|
150
|
+
| {name:date} | 日期 (YYYYMMDD) | {value:date} -> date(2024,1,15) |
|
|
151
|
+
| {name:bool} | 布尔值 | {value:bool} -> True |
|
|
152
|
+
|
|
153
|
+
:param output_template: 输出模板字典
|
|
154
|
+
:return: 解析器函数
|
|
155
|
+
"""
|
|
156
|
+
# 预处理模板:将模板字典解析为结构化规范列表,便于后续快速执行
|
|
157
|
+
# 每个元素为三元组:(输出键, 规则类型, 规则数据)
|
|
158
|
+
# 规则类型为 "static" 表示静态值,"dynamic" 表示需要从捕获组动态提取并转换
|
|
159
|
+
processed_spec: list[tuple[str, str, Any]] = []
|
|
160
|
+
# 匹配 {name:type} 格式的模板占位符,如 {value:int}
|
|
161
|
+
template_regex = re.compile(r"{(\w+):(\w*)}")
|
|
162
|
+
|
|
163
|
+
for key, template_value in output_template.items():
|
|
164
|
+
if isinstance(template_value, str):
|
|
165
|
+
# 尝试匹配动态模板占位符
|
|
166
|
+
match = template_regex.fullmatch(template_value)
|
|
167
|
+
if match:
|
|
168
|
+
group_name, type_name = match.groups()
|
|
169
|
+
# 校验类型是否存在于转换器字典中,防止运行时因未知类型而失败
|
|
170
|
+
if type_name not in TYPE_CASTERS:
|
|
171
|
+
raise ValueError(f"在类型转换器中未找到目标类型: '{type_name}'")
|
|
172
|
+
# 记录动态规则:输出键 -> (捕获组名, 目标类型)
|
|
173
|
+
processed_spec.append((key, "dynamic", (group_name, type_name)))
|
|
174
|
+
continue
|
|
175
|
+
# 非动态模板视为静态值,直接保留原值
|
|
176
|
+
processed_spec.append((key, "static", template_value))
|
|
177
|
+
|
|
178
|
+
def parser(regex_groups: dict[str, str]) -> dict[str, Any]:
|
|
179
|
+
"""
|
|
180
|
+
解析正则捕获组。
|
|
181
|
+
|
|
182
|
+
============================================================================
|
|
183
|
+
输入数据示例
|
|
184
|
+
============================================================================
|
|
185
|
+
本函数接收正则捕获组字典:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
# 假设模板是 {"value": "{value:int}", "type": "static_type"}
|
|
189
|
+
regex_groups = {"value": "123"}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
最终输出:
|
|
193
|
+
{"value": 123, "type": "static_type"}
|
|
194
|
+
"""
|
|
195
|
+
# 根据预处理后的规范,将正则捕获组转换为结构化输出字典
|
|
196
|
+
result_dict: dict[str, Any] = {}
|
|
197
|
+
for key, rule_type, rule_data in processed_spec:
|
|
198
|
+
if rule_type == "static":
|
|
199
|
+
# 静态值直接复制到结果中
|
|
200
|
+
result_dict[key] = rule_data
|
|
201
|
+
elif rule_type == "dynamic":
|
|
202
|
+
# 动态值需要从捕获组中提取并按指定类型转换
|
|
203
|
+
group_name, type_name = rule_data
|
|
204
|
+
# 校验捕获组是否存在,防止模板与正则表达式定义不一致导致 KeyError
|
|
205
|
+
if group_name not in regex_groups:
|
|
206
|
+
raise KeyError(f"在正则捕获组中未找到名为 '{group_name}' 的组")
|
|
207
|
+
raw_value = regex_groups[group_name]
|
|
208
|
+
try:
|
|
209
|
+
# 查找并调用对应的类型转换器函数
|
|
210
|
+
caster_func = TYPE_CASTERS[type_name]
|
|
211
|
+
result_dict[key] = caster_func(raw_value)
|
|
212
|
+
except (ValueError, TypeError) as e:
|
|
213
|
+
# 转换失败时抛出明确的错误信息,包含原始值和目标类型,便于定位问题
|
|
214
|
+
raise ValueError(f"转换捕获组 '{group_name}' 的值 '{raw_value}' 到类型 '{type_name}' 时失败: {e}")
|
|
215
|
+
return result_dict
|
|
216
|
+
|
|
217
|
+
return parser
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# ============================================================================
|
|
221
|
+
# 解析器函数类型定义
|
|
222
|
+
# ============================================================================
|
|
223
|
+
# 输入: 正则捕获组字典
|
|
224
|
+
# 输出: 解析后的字典
|
|
225
|
+
|
|
226
|
+
ParserFunc = Callable[[dict[str, str]], dict[str, Any]]
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@dataclass
|
|
230
|
+
class ExpressionPattern:
|
|
231
|
+
"""
|
|
232
|
+
表达式模式数据类。
|
|
233
|
+
|
|
234
|
+
============================================================================
|
|
235
|
+
配置文件示例 (这个类对应什么)
|
|
236
|
+
============================================================================
|
|
237
|
+
本类对应 patterns/*.yaml 中的模式定义:
|
|
238
|
+
|
|
239
|
+
```yaml
|
|
240
|
+
# patterns/phone.yaml
|
|
241
|
+
name: phone_cn
|
|
242
|
+
pattern: r'^1[3-9]\\d{9}$'
|
|
243
|
+
template:
|
|
244
|
+
type: phone_cn
|
|
245
|
+
value: "{value:str}"
|
|
246
|
+
```
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
name: str # 表达式名称,如 "phone_cn"
|
|
250
|
+
regex: re.Pattern # 编译后的正则表达式
|
|
251
|
+
parser_func: ParserFunc # 解析器函数
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class ExpressionRegistry:
|
|
255
|
+
r"""
|
|
256
|
+
表达式注册表。
|
|
257
|
+
|
|
258
|
+
============================================================================
|
|
259
|
+
配置文件示例 (这个类对应什么)
|
|
260
|
+
============================================================================
|
|
261
|
+
本类用于管理多个表达式模式:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
# 创建注册表
|
|
265
|
+
registry = ExpressionRegistry()
|
|
266
|
+
|
|
267
|
+
# 注册模式
|
|
268
|
+
registry.register(ExpressionPattern(
|
|
269
|
+
name="phone_cn",
|
|
270
|
+
regex=re.compile(r'^1[3-9]\\d{9}$'),
|
|
271
|
+
parser_func=create_templated_parser({"type": "phone", "value": "{value:str}"})
|
|
272
|
+
))
|
|
273
|
+
|
|
274
|
+
# 查找匹配
|
|
275
|
+
result = registry.find_match("13800138000")
|
|
276
|
+
# 返回: (ExpressionPattern, Match)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
============================================================================
|
|
280
|
+
业务场景 (什么情况下会使用这个类)
|
|
281
|
+
============================================================================
|
|
282
|
+
- 场景1: 表达式模式管理
|
|
283
|
+
管理多个预定义的表达式模式,供数据验证使用。
|
|
284
|
+
|
|
285
|
+
- 场景2: 表达式匹配
|
|
286
|
+
查找输入字符串匹配哪个预定义的表达式模式。
|
|
287
|
+
|
|
288
|
+
============================================================================
|
|
289
|
+
使用示例
|
|
290
|
+
============================================================================
|
|
291
|
+
【创建和注册】
|
|
292
|
+
```python
|
|
293
|
+
registry = ExpressionRegistry()
|
|
294
|
+
|
|
295
|
+
# 注册手机号模式
|
|
296
|
+
phone_pattern = ExpressionPattern(
|
|
297
|
+
name="phone_cn",
|
|
298
|
+
regex=re.compile(r'^1[3-9]\\d{9}$'),
|
|
299
|
+
parser_func=create_templated_parser({"type": "phone", "value": "{value:str}"})
|
|
300
|
+
)
|
|
301
|
+
registry.register(phone_pattern)
|
|
302
|
+
|
|
303
|
+
# 注册邮箱模式
|
|
304
|
+
email_pattern = ExpressionPattern(
|
|
305
|
+
name="email",
|
|
306
|
+
regex=re.compile(r'^[\w\.-]+@[\w\.-]+\.\w+$'),
|
|
307
|
+
parser_func=create_templated_parser({"type": "email", "value": "{value:str}"})
|
|
308
|
+
)
|
|
309
|
+
registry.register(email_pattern)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
【匹配使用】
|
|
313
|
+
```python
|
|
314
|
+
# 匹配手机号
|
|
315
|
+
result = registry.find_match("13800138000")
|
|
316
|
+
if result:
|
|
317
|
+
pattern, match = result
|
|
318
|
+
parsed = pattern.parser_func(match.groupdict())
|
|
319
|
+
# parsed: {"type": "phone", "value": "13800138000"}
|
|
320
|
+
```
|
|
321
|
+
"""
|
|
322
|
+
|
|
323
|
+
def __init__(self) -> None:
|
|
324
|
+
# 使用列表存储已注册的表达式模式,保持注册顺序
|
|
325
|
+
# 匹配时按注册顺序遍历,先注册的先匹配
|
|
326
|
+
self._patterns: list[ExpressionPattern] = []
|
|
327
|
+
|
|
328
|
+
def register(self, pattern: ExpressionPattern) -> None:
|
|
329
|
+
"""
|
|
330
|
+
注册表达式模式。
|
|
331
|
+
|
|
332
|
+
:param pattern: ExpressionPattern 实例
|
|
333
|
+
"""
|
|
334
|
+
# 将模式追加到内部列表,后续 find_match 会按此顺序遍历
|
|
335
|
+
self._patterns.append(pattern)
|
|
336
|
+
|
|
337
|
+
def find_match(self, value: str) -> tuple[ExpressionPattern, re.Match] | None:
|
|
338
|
+
"""
|
|
339
|
+
查找匹配的模式。
|
|
340
|
+
|
|
341
|
+
============================================================================
|
|
342
|
+
数据流 (输入如何变成输出)
|
|
343
|
+
============================================================================
|
|
344
|
+
输入参数:
|
|
345
|
+
- value: str,待匹配的字符串
|
|
346
|
+
示例值: "13800138000"
|
|
347
|
+
|
|
348
|
+
处理步骤:
|
|
349
|
+
1. 遍历所有已注册的模式
|
|
350
|
+
2. 对每个模式使用 fullmatch 尝试匹配
|
|
351
|
+
3. 返回第一个匹配的模式和匹配结果
|
|
352
|
+
|
|
353
|
+
最终输出: Optional[Tuple[ExpressionPattern, re.Match]]
|
|
354
|
+
示例:
|
|
355
|
+
find_match("13800138000") # 返回 (phone_pattern, match)
|
|
356
|
+
find_match("invalid") # 返回 None
|
|
357
|
+
"""
|
|
358
|
+
# 按注册顺序遍历所有模式,使用 fullmatch 确保整个字符串完全匹配
|
|
359
|
+
# 先去掉输入字符串首尾空白,避免空格导致匹配失败
|
|
360
|
+
for pattern in self._patterns:
|
|
361
|
+
match = pattern.regex.fullmatch(value.strip())
|
|
362
|
+
if match:
|
|
363
|
+
# 返回第一个匹配成功的模式及其匹配结果对象
|
|
364
|
+
return pattern, match
|
|
365
|
+
# 没有任何模式匹配成功,返回 None
|
|
366
|
+
return None
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
"""@fileoverview 正则 flags 字符串解析(domain 纯逻辑,单一事实源)
|
|
17
|
+
|
|
18
|
+
被 core/services/domain 多处消费(regex_extract、regex/reader、validation/extractors、
|
|
19
|
+
dag/executor、constraints/regex),防各处自行实现漂移出子串匹配等缺陷。
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import re
|
|
25
|
+
|
|
26
|
+
# flags 短格式字符到 re 标志位的映射
|
|
27
|
+
_FLAG_SHORT_MAP = {"i": re.IGNORECASE, "m": re.MULTILINE, "s": re.DOTALL}
|
|
28
|
+
# flags 长格式名称到 re 标志位的映射
|
|
29
|
+
_FLAG_LONG_MAP = {"ignorecase": re.IGNORECASE, "multiline": re.MULTILINE, "dotall": re.DOTALL}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def parse_regex_flags(flags_str: str | None) -> int:
|
|
33
|
+
"""把 flags 配置字符串解析为 re 模块标志位。
|
|
34
|
+
|
|
35
|
+
按 逗号/空白 切分后整词匹配(大小写不敏感),支持三种格式:
|
|
36
|
+
- 短格式:"i" -> IGNORECASE
|
|
37
|
+
- 组合短格式:"im" -> IGNORECASE | MULTILINE(逐字符展开)
|
|
38
|
+
- 长格式:"ignorecase" -> IGNORECASE
|
|
39
|
+
|
|
40
|
+
不得用子串匹配:`"i" in "multiline"` 为真会导致长格式误开 IGNORECASE;
|
|
41
|
+
未知 token 整体忽略,不逐字符猜测。case_sensitive 的补充语义由调用方自行叠加。
|
|
42
|
+
"""
|
|
43
|
+
flags = 0
|
|
44
|
+
tokens = [tok for tok in re.split(r"[,\s]+", str(flags_str or "").strip()) if tok]
|
|
45
|
+
for tok in tokens:
|
|
46
|
+
lowered = tok.lower()
|
|
47
|
+
if lowered in _FLAG_LONG_MAP:
|
|
48
|
+
flags |= _FLAG_LONG_MAP[lowered]
|
|
49
|
+
elif all(ch in _FLAG_SHORT_MAP for ch in lowered):
|
|
50
|
+
# 组合短格式(如 "im")逐字符展开;全部字符都是合法缩写才生效
|
|
51
|
+
for ch in lowered:
|
|
52
|
+
flags |= _FLAG_SHORT_MAP[ch]
|
|
53
|
+
return flags
|