gatewaysdk 0.3.2__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.
- gatewaysdk/__init__.py +540 -0
- gatewaysdk/adapter/__init__.py +15 -0
- gatewaysdk/adapter/base.py +94 -0
- gatewaysdk/adapter/messages.py +270 -0
- gatewaysdk/adapter/triplet.py +1026 -0
- gatewaysdk/algorithm/__init__.py +39 -0
- gatewaysdk/algorithm/apo/__init__.py +5 -0
- gatewaysdk/algorithm/apo/apo.py +898 -0
- gatewaysdk/algorithm/apo/prompts/apply_edit_variant01.poml +22 -0
- gatewaysdk/algorithm/apo/prompts/apply_edit_variant02.poml +18 -0
- gatewaysdk/algorithm/apo/prompts/text_gradient_variant01.poml +18 -0
- gatewaysdk/algorithm/apo/prompts/text_gradient_variant02.poml +16 -0
- gatewaysdk/algorithm/apo/prompts/text_gradient_variant03.poml +107 -0
- gatewaysdk/algorithm/base.py +262 -0
- gatewaysdk/algorithm/decorator.py +264 -0
- gatewaysdk/algorithm/evals/__init__.py +7 -0
- gatewaysdk/algorithm/evals/evals.py +217 -0
- gatewaysdk/algorithm/fast.py +250 -0
- gatewaysdk/algorithm/gepa/__init__.py +61 -0
- gatewaysdk/algorithm/gepa/adapter.py +495 -0
- gatewaysdk/algorithm/gepa/gepa.py +570 -0
- gatewaysdk/algorithm/gepa/lib/__init__.py +18 -0
- gatewaysdk/algorithm/gepa/lib/adapters/README.md +12 -0
- gatewaysdk/algorithm/gepa/lib/adapters/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/README.md +341 -0
- gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/__init__.py +1 -0
- gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
- gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/requirements.txt +1 -0
- gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/README.md +0 -0
- gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/default_adapter.py +209 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/README.md +7 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/dspy_adapter.py +307 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/README.md +99 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/full_program_adapter.py +268 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/GEPA_RAG.md +621 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/__init__.py +56 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
- gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/README.md +552 -0
- gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/__init__.py +37 -0
- gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_adapter.py +699 -0
- gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_client.py +364 -0
- gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/README.md +9 -0
- gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
- gatewaysdk/algorithm/gepa/lib/api.py +382 -0
- gatewaysdk/algorithm/gepa/lib/core/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/core/adapter.py +180 -0
- gatewaysdk/algorithm/gepa/lib/core/data_loader.py +74 -0
- gatewaysdk/algorithm/gepa/lib/core/engine.py +379 -0
- gatewaysdk/algorithm/gepa/lib/core/result.py +233 -0
- gatewaysdk/algorithm/gepa/lib/core/state.py +636 -0
- gatewaysdk/algorithm/gepa/lib/examples/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/examples/aime.py +24 -0
- gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/eval_default.py +111 -0
- gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/instruction_prompt.txt +9 -0
- gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/optimal_prompt.txt +24 -0
- gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/train_anymaths.py +177 -0
- gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/arc_agi.ipynb +25705 -0
- gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/example.ipynb +348 -0
- gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/__init__.py +4 -0
- gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/mcp_optimization_example.py +456 -0
- gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/RAG_GUIDE.md +613 -0
- gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/__init__.py +9 -0
- gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/rag_optimization.py +820 -0
- gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/requirements-rag.txt +29 -0
- gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/instruction_prompt.txt +16 -0
- gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/terminus.txt +9 -0
- gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/train_terminus.py +161 -0
- gatewaysdk/algorithm/gepa/lib/gepa_utils.py +117 -0
- gatewaysdk/algorithm/gepa/lib/logging/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/logging/experiment_tracker.py +187 -0
- gatewaysdk/algorithm/gepa/lib/logging/logger.py +75 -0
- gatewaysdk/algorithm/gepa/lib/logging/utils.py +103 -0
- gatewaysdk/algorithm/gepa/lib/proposer/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/proposer/base.py +31 -0
- gatewaysdk/algorithm/gepa/lib/proposer/merge.py +357 -0
- gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/base.py +49 -0
- gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/reflective_mutation.py +176 -0
- gatewaysdk/algorithm/gepa/lib/py.typed +0 -0
- gatewaysdk/algorithm/gepa/lib/strategies/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/strategies/batch_sampler.py +77 -0
- gatewaysdk/algorithm/gepa/lib/strategies/candidate_selector.py +50 -0
- gatewaysdk/algorithm/gepa/lib/strategies/component_selector.py +36 -0
- gatewaysdk/algorithm/gepa/lib/strategies/eval_policy.py +64 -0
- gatewaysdk/algorithm/gepa/lib/strategies/instruction_proposal.py +126 -0
- gatewaysdk/algorithm/gepa/lib/utils/__init__.py +10 -0
- gatewaysdk/algorithm/gepa/lib/utils/stop_condition.py +196 -0
- gatewaysdk/algorithm/gepa/tracing.py +105 -0
- gatewaysdk/algorithm/utils.py +177 -0
- gatewaysdk/algorithm/verl/__init__.py +5 -0
- gatewaysdk/algorithm/verl/interface.py +202 -0
- gatewaysdk/automations.py +111 -0
- gatewaysdk/benchmark_hub/README.md +88 -0
- gatewaysdk/benchmark_hub/__init__.py +119 -0
- gatewaysdk/benchmark_hub/_archive.py +189 -0
- gatewaysdk/benchmark_hub/_tar.py +6 -0
- gatewaysdk/benchmark_hub/client.py +992 -0
- gatewaysdk/benchmark_hub/dispatch_shard.py +34 -0
- gatewaysdk/benchmark_hub/eval_config.py +20 -0
- gatewaysdk/benchmark_hub/evals.py +351 -0
- gatewaysdk/benchmark_hub/harbor_adapter.py +673 -0
- gatewaysdk/benchmark_hub/path_utils.py +81 -0
- gatewaysdk/benchmark_hub/save_utils.py +101 -0
- gatewaysdk/benchmark_hub/verifiers_adapter.py +327 -0
- gatewaysdk/benchmark_hub/versioning.py +72 -0
- gatewaysdk/build.py +515 -0
- gatewaysdk/cli/__init__.py +58 -0
- gatewaysdk/cli/agent_runner.py +132 -0
- gatewaysdk/cli/http_client.py +115 -0
- gatewaysdk/cli/platform.py +4086 -0
- gatewaysdk/cli/prometheus.py +115 -0
- gatewaysdk/cli/release_gate.py +215 -0
- gatewaysdk/cli/store.py +131 -0
- gatewaysdk/cli/vllm.py +29 -0
- gatewaysdk/client.py +406 -0
- gatewaysdk/config.py +348 -0
- gatewaysdk/connectors/__init__.py +25 -0
- gatewaysdk/connectors/client.py +203 -0
- gatewaysdk/connectors/skill.py +25 -0
- gatewaysdk/connectors/template.py +106 -0
- gatewaysdk/context.py +606 -0
- gatewaysdk/emitter/__init__.py +43 -0
- gatewaysdk/emitter/annotation.py +370 -0
- gatewaysdk/emitter/exception.py +54 -0
- gatewaysdk/emitter/message.py +61 -0
- gatewaysdk/emitter/object.py +117 -0
- gatewaysdk/emitter/reward.py +320 -0
- gatewaysdk/env_var.py +156 -0
- gatewaysdk/environment/__init__.py +108 -0
- gatewaysdk/environment/_bundle.py +281 -0
- gatewaysdk/environment/_harbor.py +276 -0
- gatewaysdk/environment/_materialize.py +97 -0
- gatewaysdk/environment/_tar.py +33 -0
- gatewaysdk/environment/_world.py +346 -0
- gatewaysdk/environment/core.py +527 -0
- gatewaysdk/environment/errors.py +58 -0
- gatewaysdk/environment/runtime.py +150 -0
- gatewaysdk/environment/schema/__init__.py +35 -0
- gatewaysdk/environment/schema/__main__.py +225 -0
- gatewaysdk/environment/schema/_slack_fidelity.py +116 -0
- gatewaysdk/environment/schema/_slack_scenario.py +183 -0
- gatewaysdk/environment/schema/api.py +1628 -0
- gatewaysdk/environment/schema/batch.py +755 -0
- gatewaysdk/environment/schema/compiler.py +615 -0
- gatewaysdk/environment/schema/conform.py +428 -0
- gatewaysdk/environment/schema/connector.py +777 -0
- gatewaysdk/environment/schema/connectors/apple-business-manager/handlers.py +17 -0
- gatewaysdk/environment/schema/connectors/apple-business-manager/parity.json +28 -0
- gatewaysdk/environment/schema/connectors/apple-business-manager/provenance.json +233 -0
- gatewaysdk/environment/schema/connectors/apple-business-manager/scope.toml +112 -0
- gatewaysdk/environment/schema/connectors/apple-business-manager/world.json +1380 -0
- gatewaysdk/environment/schema/connectors/base.json +28 -0
- gatewaysdk/environment/schema/connectors/custom/handlers.py +18 -0
- gatewaysdk/environment/schema/connectors/custom/world.json +123 -0
- gatewaysdk/environment/schema/connectors/github/handlers.py +81 -0
- gatewaysdk/environment/schema/connectors/github/parity.json +37 -0
- gatewaysdk/environment/schema/connectors/github/provenance.json +134 -0
- gatewaysdk/environment/schema/connectors/github/scope.toml +158 -0
- gatewaysdk/environment/schema/connectors/github/world.json +5869 -0
- gatewaysdk/environment/schema/connectors/google-calendar/handlers.py +23 -0
- gatewaysdk/environment/schema/connectors/google-calendar/world.json +209 -0
- gatewaysdk/environment/schema/connectors/google-drive/handlers.py +18 -0
- gatewaysdk/environment/schema/connectors/google-drive/world.json +286 -0
- gatewaysdk/environment/schema/connectors/jamf/handlers.py +34 -0
- gatewaysdk/environment/schema/connectors/jamf/parity.json +49 -0
- gatewaysdk/environment/schema/connectors/jamf/provenance.json +181 -0
- gatewaysdk/environment/schema/connectors/jamf/scope.toml +128 -0
- gatewaysdk/environment/schema/connectors/jamf/world.json +2218 -0
- gatewaysdk/environment/schema/connectors/jira/handlers.py +28 -0
- gatewaysdk/environment/schema/connectors/jira/world.json +389 -0
- gatewaysdk/environment/schema/connectors/kandji/handlers.py +38 -0
- gatewaysdk/environment/schema/connectors/kandji/parity.json +29 -0
- gatewaysdk/environment/schema/connectors/kandji/provenance.json +152 -0
- gatewaysdk/environment/schema/connectors/kandji/scope.toml +120 -0
- gatewaysdk/environment/schema/connectors/kandji/world.json +765 -0
- gatewaysdk/environment/schema/connectors/linear/handlers.py +20 -0
- gatewaysdk/environment/schema/connectors/linear/world.json +363 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/fidelity.json +45 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/handlers.py +1 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/provenance.json +851 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/scope.md +231 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/teams-types.json +1710 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/world.json +858 -0
- gatewaysdk/environment/schema/connectors/netsuite/handlers.py +32 -0
- gatewaysdk/environment/schema/connectors/netsuite/parity.json +50 -0
- gatewaysdk/environment/schema/connectors/netsuite/provenance.json +137 -0
- gatewaysdk/environment/schema/connectors/netsuite/scope.toml +78 -0
- gatewaysdk/environment/schema/connectors/netsuite/world.json +16243 -0
- gatewaysdk/environment/schema/connectors/salesforce/handlers.py +806 -0
- gatewaysdk/environment/schema/connectors/salesforce/parity.json +46 -0
- gatewaysdk/environment/schema/connectors/salesforce/provenance.json +143 -0
- gatewaysdk/environment/schema/connectors/salesforce/scope.toml +174 -0
- gatewaysdk/environment/schema/connectors/salesforce/world.json +4695 -0
- gatewaysdk/environment/schema/connectors/slack/MODEL.md +236 -0
- gatewaysdk/environment/schema/connectors/slack/capabilities.json +1039 -0
- gatewaysdk/environment/schema/connectors/slack/handlers.py +762 -0
- gatewaysdk/environment/schema/connectors/slack/parity.json +65 -0
- gatewaysdk/environment/schema/connectors/slack/provenance.json +294 -0
- gatewaysdk/environment/schema/connectors/slack/scope.toml +44 -0
- gatewaysdk/environment/schema/connectors/slack/world.json +4993 -0
- gatewaysdk/environment/schema/connectors/workday/handlers.py +75 -0
- gatewaysdk/environment/schema/connectors/workday/parity.json +36 -0
- gatewaysdk/environment/schema/connectors/workday/provenance.json +179 -0
- gatewaysdk/environment/schema/connectors/workday/scope.toml +180 -0
- gatewaysdk/environment/schema/connectors/workday/world.json +806 -0
- gatewaysdk/environment/schema/data.py +593 -0
- gatewaysdk/environment/schema/extract.py +263 -0
- gatewaysdk/environment/schema/host.py +2437 -0
- gatewaysdk/environment/schema/host_surfaces.py +275 -0
- gatewaysdk/environment/schema/platform.py +1272 -0
- gatewaysdk/environment/schema/scaffold.py +250 -0
- gatewaysdk/environment/schema/skill.py +118 -0
- gatewaysdk/environment/schema/skills/agents/connector-template-author.md +202 -0
- gatewaysdk/environment/schema/skills/connector-schema-authoring/CONNECTORS.md +331 -0
- gatewaysdk/environment/schema/skills/connector-schema-authoring/CONVENTIONS.md +100 -0
- gatewaysdk/environment/schema/skills/connector-schema-authoring/SKILL.md +313 -0
- gatewaysdk/environment/schema/skills/world-data-ingestion/ROWS.md +223 -0
- gatewaysdk/environment/schema/skills/world-data-ingestion/SKILL.md +293 -0
- gatewaysdk/environment/schema/skills/worlds-getting-started/SKILL.md +390 -0
- gatewaysdk/environment/schema/slack.py +757 -0
- gatewaysdk/environment/schema/snapshot.py +479 -0
- gatewaysdk/environment/schema/store.py +904 -0
- gatewaysdk/environment/schema/tasks.py +525 -0
- gatewaysdk/environment/schema/tools_world.py +322 -0
- gatewaysdk/environment/schema/validation.py +577 -0
- gatewaysdk/environment/schema/worker.py +78 -0
- gatewaysdk/environment/schema/workers.py +500 -0
- gatewaysdk/environment/schema/world_tests.py +336 -0
- gatewaysdk/environments/__init__.py +24 -0
- gatewaysdk/environments/client.py +486 -0
- gatewaysdk/environments/types.py +318 -0
- gatewaysdk/execution/__init__.py +15 -0
- gatewaysdk/execution/base.py +64 -0
- gatewaysdk/execution/client_server.py +443 -0
- gatewaysdk/execution/events.py +69 -0
- gatewaysdk/execution/inter_process.py +16 -0
- gatewaysdk/execution/shared_memory.py +282 -0
- gatewaysdk/experiments/__init__.py +90 -0
- gatewaysdk/experiments/assignment.py +177 -0
- gatewaysdk/experiments/client.py +877 -0
- gatewaysdk/experiments/exposure.py +222 -0
- gatewaysdk/experiments/types.py +81 -0
- gatewaysdk/importers/__init__.py +49 -0
- gatewaysdk/importers/_normalize.py +91 -0
- gatewaysdk/importers/builder.py +173 -0
- gatewaysdk/importers/client.py +95 -0
- gatewaysdk/importers/langsmith.py +196 -0
- gatewaysdk/importers/recipes.py +249 -0
- gatewaysdk/instrumentation/__init__.py +300 -0
- gatewaysdk/instrumentation/agentops.py +314 -0
- gatewaysdk/instrumentation/agentops_langchain.py +45 -0
- gatewaysdk/instrumentation/base.py +119 -0
- gatewaysdk/instrumentation/litellm.py +83 -0
- gatewaysdk/instrumentation/registry.py +273 -0
- gatewaysdk/instrumentation/vllm.py +81 -0
- gatewaysdk/instrumentation/weave.py +500 -0
- gatewaysdk/integrations/__init__.py +15 -0
- gatewaysdk/integrations/gateway/__init__.py +11 -0
- gatewaysdk/integrations/gateway/client.py +171 -0
- gatewaysdk/integrations/tool_access.py +549 -0
- gatewaysdk/litagent/__init__.py +11 -0
- gatewaysdk/litagent/decorator.py +536 -0
- gatewaysdk/litagent/litagent.py +252 -0
- gatewaysdk/llm_proxy.py +1742 -0
- gatewaysdk/logging.py +370 -0
- gatewaysdk/memory.py +278 -0
- gatewaysdk/personas.py +186 -0
- gatewaysdk/platform/__init__.py +17 -0
- gatewaysdk/platform/builder.py +221 -0
- gatewaysdk/platform/compatibility.py +67 -0
- gatewaysdk/platform/manifest.py +185 -0
- gatewaysdk/platform/orchestrator.py +901 -0
- gatewaysdk/platform/registry.py +122 -0
- gatewaysdk/platform/worker.py +864 -0
- gatewaysdk/replay/__init__.py +1032 -0
- gatewaysdk/replay/pytest.py +56 -0
- gatewaysdk/reward.py +7 -0
- gatewaysdk/run.py +1781 -0
- gatewaysdk/runner/__init__.py +11 -0
- gatewaysdk/runner/agent.py +878 -0
- gatewaysdk/runner/base.py +182 -0
- gatewaysdk/runner/legacy.py +309 -0
- gatewaysdk/security.py +700 -0
- gatewaysdk/semconv.py +170 -0
- gatewaysdk/server.py +399 -0
- gatewaysdk/sessions.py +282 -0
- gatewaysdk/store/__init__.py +45 -0
- gatewaysdk/store/base.py +908 -0
- gatewaysdk/store/client_server.py +2093 -0
- gatewaysdk/store/collection/__init__.py +30 -0
- gatewaysdk/store/collection/base.py +587 -0
- gatewaysdk/store/collection/memory.py +970 -0
- gatewaysdk/store/collection/mongo.py +1412 -0
- gatewaysdk/store/collection_based.py +1823 -0
- gatewaysdk/store/gateway.py +983 -0
- gatewaysdk/store/gateway_listener.py +465 -0
- gatewaysdk/store/listener.py +58 -0
- gatewaysdk/store/memory.py +396 -0
- gatewaysdk/store/mongo.py +165 -0
- gatewaysdk/store/redis_stream.py +517 -0
- gatewaysdk/store/sqlite.py +3 -0
- gatewaysdk/store/threading.py +370 -0
- gatewaysdk/store/utils.py +142 -0
- gatewaysdk/tracer/__init__.py +14 -0
- gatewaysdk/tracer/base.py +286 -0
- gatewaysdk/tracer/dummy.py +106 -0
- gatewaysdk/tracer/otel.py +559 -0
- gatewaysdk/tracing/__init__.py +110 -0
- gatewaysdk/tracing/api.py +808 -0
- gatewaysdk/tracing/attributes.py +9 -0
- gatewaysdk/tracing/context.py +272 -0
- gatewaysdk/tracing/exporters/__init__.py +10 -0
- gatewaysdk/tracing/exporters/gateway.py +228 -0
- gatewaysdk/tracing/identity.py +288 -0
- gatewaysdk/tracing/init.py +620 -0
- gatewaysdk/tracing/instrumentors/__init__.py +15 -0
- gatewaysdk/tracing/instrumentors/claude_agent_sdk.py +766 -0
- gatewaysdk/tracing/instrumentors/instrumentation_principles.md +294 -0
- gatewaysdk/tracing/instrumentors/registry.py +352 -0
- gatewaysdk/tracing/mapping.py +729 -0
- gatewaysdk/tracing/processors.py +393 -0
- gatewaysdk/tracing/push.py +617 -0
- gatewaysdk/tracing/push_models.py +247 -0
- gatewaysdk/tracing/semconv.py +294 -0
- gatewaysdk/tracing/span_builder.py +356 -0
- gatewaysdk/trainer/__init__.py +6 -0
- gatewaysdk/trainer/init_utils.py +263 -0
- gatewaysdk/trainer/legacy.py +359 -0
- gatewaysdk/trainer/registry.py +12 -0
- gatewaysdk/trainer/trainer.py +638 -0
- gatewaysdk/types/__init__.py +63 -0
- gatewaysdk/types/core.py +556 -0
- gatewaysdk/types/resources.py +204 -0
- gatewaysdk/types/tracer.py +515 -0
- gatewaysdk/types/tracing.py +162 -0
- gatewaysdk/users.py +251 -0
- gatewaysdk/utils/__init__.py +1 -0
- gatewaysdk/utils/id.py +18 -0
- gatewaysdk/utils/metrics.py +1025 -0
- gatewaysdk/utils/otel.py +550 -0
- gatewaysdk/utils/otlp.py +556 -0
- gatewaysdk/utils/redact.py +22 -0
- gatewaysdk/utils/server_launcher.py +1045 -0
- gatewaysdk/utils/system_snapshot.py +90 -0
- gatewaysdk/verl/__init__.py +8 -0
- gatewaysdk/verl/__main__.py +6 -0
- gatewaysdk/verl/async_server.py +46 -0
- gatewaysdk/verl/config.yaml +27 -0
- gatewaysdk/verl/daemon.py +1154 -0
- gatewaysdk/verl/dataset.py +44 -0
- gatewaysdk/verl/entrypoint.py +248 -0
- gatewaysdk/verl/trainer.py +549 -0
- gatewaysdk/world_browser.py +748 -0
- gatewaysdk/world_data.py +453 -0
- gatewaysdk/world_sessions.py +978 -0
- gatewaysdk/world_tasks.py +285 -0
- gatewaysdk/world_tools.py +115 -0
- gatewaysdk/worlds.py +639 -0
- gatewaysdk-0.3.2.dist-info/METADATA +236 -0
- gatewaysdk-0.3.2.dist-info/RECORD +376 -0
- gatewaysdk-0.3.2.dist-info/WHEEL +4 -0
- gatewaysdk-0.3.2.dist-info/entry_points.txt +5 -0
- gatewaysdk-0.3.2.dist-info/licenses/LICENSE +19 -0
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
"""Rows into a world, through the world's own gate.
|
|
2
|
+
|
|
3
|
+
Any agent that holds data — a vendor export, a real run's call log, a hand-made
|
|
4
|
+
fixture — should be able to put it into a world without knowing the world's
|
|
5
|
+
layout. This module is that door. It reads rows from files in one of three
|
|
6
|
+
shapes, works out what kind of world it is looking at, and writes the rows the
|
|
7
|
+
way that kind seeds:
|
|
8
|
+
|
|
9
|
+
* a **schema** world (``gateway-world/1``) seeds from ``data/initial.json``, an
|
|
10
|
+
``apply_batch`` payload. Rows become a new batch version appended to the
|
|
11
|
+
existing mutations, applied in a scratch copy first exactly as
|
|
12
|
+
:func:`connector.ingest` does, so a row the contract refuses is refused
|
|
13
|
+
with the store's own diagnostic and nothing on disk changes;
|
|
14
|
+
* a **mock-tools** world seeds from ``db/schema.sql`` + ``db/seed.sql``. Rows
|
|
15
|
+
become ``INSERT`` statements appended to the seed, columns checked against
|
|
16
|
+
the schema, and the seed is proven to load in a scratch database first;
|
|
17
|
+
* an **api** world's ``seed/base.json`` and a **harbor** tree's task fixtures
|
|
18
|
+
are generated or per-task; the import refuses and names what to edit.
|
|
19
|
+
|
|
20
|
+
The same row planning seeds a *live* session: :func:`seed_batch` builds the
|
|
21
|
+
batch a running store applies atomically, and :func:`seed_sqlite` executes
|
|
22
|
+
the inserts against a task's live sqlite.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
import json
|
|
29
|
+
import re
|
|
30
|
+
import shutil
|
|
31
|
+
import sqlite3
|
|
32
|
+
import tempfile
|
|
33
|
+
import uuid
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from typing import Any, Iterable
|
|
36
|
+
|
|
37
|
+
from .compiler import bundle_mode, check_bundle, compile_bundle
|
|
38
|
+
from .store import SQLiteStore
|
|
39
|
+
from .validation import ContractError, digest, encoded, fail, journal_paths, read_json
|
|
40
|
+
|
|
41
|
+
MODES = ("append", "replace")
|
|
42
|
+
MERGES = ("upsert", "fill")
|
|
43
|
+
#: The inline lane's caps (the platform's seed and import calls share them): a local
|
|
44
|
+
#: directory import above either is refused; larger data is a platform batch.
|
|
45
|
+
INLINE_ROWS = 5000
|
|
46
|
+
INLINE_BYTES = 4 * 1024 * 1024
|
|
47
|
+
KIND_SCHEMA = "schema"
|
|
48
|
+
KIND_MOCK_TOOLS = "mock-tools"
|
|
49
|
+
KIND_API = "api"
|
|
50
|
+
KIND_HARBOR = "harbor"
|
|
51
|
+
KIND_UNKNOWN = "unknown"
|
|
52
|
+
MAPPING = {"id": "data-import", "version": "1"}
|
|
53
|
+
#: Left out of scratch copies: captures, caches, and the snapshots (rebuilt from the data).
|
|
54
|
+
SCRATCH_IGNORE = ("captures", "__pycache__", ".gateway", "state.sqlite", "state.json", ".state.sqlite.*")
|
|
55
|
+
|
|
56
|
+
Rows = dict[str, list[dict]]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------- rows files
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def parse_rows(paths: Iterable[str | Path], *, entity: str | None = None) -> Rows:
|
|
63
|
+
"""Read rows from files into ``{entity: [row, …]}``.
|
|
64
|
+
|
|
65
|
+
Each file is one of: a JSON object ``{"<entity>": [row, …]}``; JSONL where
|
|
66
|
+
every line is ``{"entity": "<name>", "row": {…}}``; or — only with
|
|
67
|
+
``entity`` — a JSON array or JSONL of bare rows. Several files merge in
|
|
68
|
+
order. A file that fits none of these is refused, naming the file. The
|
|
69
|
+
files are streamed (:func:`batch.iter_rows`); this holds the result.
|
|
70
|
+
"""
|
|
71
|
+
from .batch import iter_rows
|
|
72
|
+
|
|
73
|
+
rows: Rows = {}
|
|
74
|
+
seen: dict[str, int] = {}
|
|
75
|
+
for name, row in iter_rows([str(path) for path in paths], entity=entity, seen=seen):
|
|
76
|
+
rows.setdefault(name, []).append(row)
|
|
77
|
+
for name in seen:
|
|
78
|
+
rows.setdefault(name, []) # an entity named with no rows: a replace empties it
|
|
79
|
+
return rows
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ---------------------------------------------------------------- world kinds
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def world_kind(root: str | Path) -> str:
|
|
86
|
+
"""``schema`` (gateway-world/1), ``mock-tools``, ``api``, ``harbor`` or ``unknown``."""
|
|
87
|
+
root = Path(root)
|
|
88
|
+
if bundle_mode(root) is not None:
|
|
89
|
+
return KIND_SCHEMA
|
|
90
|
+
if (root / "gateway-api-world.toml").is_file():
|
|
91
|
+
return KIND_API
|
|
92
|
+
if (root / "gateway-env.toml").is_file():
|
|
93
|
+
return KIND_MOCK_TOOLS
|
|
94
|
+
if list(root.glob("tasks/*/task.toml")) or list(root.glob("*/task.toml")):
|
|
95
|
+
return KIND_HARBOR
|
|
96
|
+
return KIND_UNKNOWN
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _refuse_kind(kind: str, root: Path) -> None:
|
|
100
|
+
if kind == KIND_API:
|
|
101
|
+
fail(
|
|
102
|
+
"kind",
|
|
103
|
+
"This is an api world: its seed/base.json is generated from the authoring world's "
|
|
104
|
+
"data/initial.json. Import into the world directory that holds connector.toml, then "
|
|
105
|
+
"`gateway bench push` it again",
|
|
106
|
+
str(root),
|
|
107
|
+
)
|
|
108
|
+
if kind == KIND_HARBOR:
|
|
109
|
+
fail(
|
|
110
|
+
"kind",
|
|
111
|
+
"This is a harbor task tree: each task seeds from its own fixtures under "
|
|
112
|
+
"tasks/<task>/environment/ (schema.sql, seed.sql, files). Edit those",
|
|
113
|
+
str(root),
|
|
114
|
+
)
|
|
115
|
+
fail(
|
|
116
|
+
"kind",
|
|
117
|
+
f"{root} is not a world this command knows: no gateway-env.toml, gateway-api-world.toml "
|
|
118
|
+
"or tasks/*/task.toml",
|
|
119
|
+
str(root),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ---------------------------------------------------------------- import
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def import_rows(root: str | Path, rows: Rows, *, mode: str = "append", batch_id: str | None = None) -> dict:
|
|
127
|
+
"""Write ``rows`` into the world at ``root`` the way its kind seeds.
|
|
128
|
+
|
|
129
|
+
``append`` adds rows, upserting by primary key; ``replace`` replaces the
|
|
130
|
+
named entities wholesale. Nothing is written until the result has been
|
|
131
|
+
proven to load in a scratch copy.
|
|
132
|
+
"""
|
|
133
|
+
root = Path(root)
|
|
134
|
+
if mode not in MODES:
|
|
135
|
+
fail("mode", f"mode must be one of {', '.join(MODES)}", "/mode")
|
|
136
|
+
if not isinstance(rows, dict) or not all(isinstance(v, list) for v in rows.values()):
|
|
137
|
+
fail("rows", "rows must map entity names to arrays of rows", "/rows")
|
|
138
|
+
kind = world_kind(root)
|
|
139
|
+
if kind == KIND_SCHEMA:
|
|
140
|
+
return _import_schema(root, rows, mode, batch_id)
|
|
141
|
+
if kind == KIND_MOCK_TOOLS:
|
|
142
|
+
return _import_mock_tools(root, rows, mode)
|
|
143
|
+
_refuse_kind(kind, root)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _import_schema(root: Path, rows: Rows, mode: str, batch_id: str | None) -> dict:
|
|
147
|
+
"""Rows into a schema world, landing after everything the world already replays.
|
|
148
|
+
|
|
149
|
+
With only ``data/initial.json`` the import folds into it (``w.initial.v2``,
|
|
150
|
+
``v3``, ...). Once refresh journals exist under ``data/batches/`` they are
|
|
151
|
+
immutable and replay *after* the initial batch, so the import is written as
|
|
152
|
+
the next journal instead: a replace deletes rows those journals hold, and
|
|
153
|
+
an append upserts against them, rather than being overwritten on replay.
|
|
154
|
+
"""
|
|
155
|
+
from .snapshot import pin_path
|
|
156
|
+
|
|
157
|
+
if pin_path(root).is_file():
|
|
158
|
+
fail(
|
|
159
|
+
"snapshot_pinned",
|
|
160
|
+
"This world's data is a hosted snapshot (db/snapshot.json), which its data files "
|
|
161
|
+
"cannot reproduce: import rows through the platform (`gateway worlds data import "
|
|
162
|
+
"<slug>` or import_world_data) instead of into the checkout",
|
|
163
|
+
"/db/snapshot.json",
|
|
164
|
+
)
|
|
165
|
+
contract = compile_bundle(root)
|
|
166
|
+
journals = journal_paths(root)
|
|
167
|
+
initial_path = root / "data/initial.json"
|
|
168
|
+
previous = read_json(initial_path) if initial_path.is_file() else None
|
|
169
|
+
prior = list(previous["mutations"]) if previous else []
|
|
170
|
+
prior_id = previous["id"] if previous else f"{contract.metadata['package']['id']}.initial.v0"
|
|
171
|
+
refreshed = [m for path in journals[1:] for m in read_json(path)["mutations"]] if previous else []
|
|
172
|
+
fresh = _plan(contract, rows, mode, _held(contract, prior + refreshed))
|
|
173
|
+
if not fresh:
|
|
174
|
+
return {"kind": KIND_SCHEMA, "batch": prior_id, "entity_counts": entity_counts(root), "changed": False}
|
|
175
|
+
if mode == "replace" and not refreshed:
|
|
176
|
+
# Folded into initial.json, a replace is written compactly: the named
|
|
177
|
+
# entities' old mutations are dropped and the rows inserted afresh.
|
|
178
|
+
prior = [m for m in prior if m.get("entity") not in rows]
|
|
179
|
+
fresh = _plan(contract, rows, mode, _held(contract, prior))
|
|
180
|
+
match = re.search(r"\.v(\d+)$", prior_id)
|
|
181
|
+
version = int(match.group(1)) + 1 if match else 1
|
|
182
|
+
package = contract.metadata["package"]["id"]
|
|
183
|
+
if refreshed:
|
|
184
|
+
batch = {
|
|
185
|
+
"id": batch_id or f"{package}.import.{len(journals):06d}",
|
|
186
|
+
"target": contract.target,
|
|
187
|
+
"mapping": MAPPING,
|
|
188
|
+
"coverage": "partial",
|
|
189
|
+
"mutations": fresh,
|
|
190
|
+
}
|
|
191
|
+
written = Path("data/batches") / f"{len(journals):06d}-{digest(batch['id'].encode())[:16]}.json"
|
|
192
|
+
payload = encoded(batch)
|
|
193
|
+
else:
|
|
194
|
+
batch = {
|
|
195
|
+
"id": batch_id or f"{package}.initial.v{version}",
|
|
196
|
+
"target": contract.target,
|
|
197
|
+
"mapping": MAPPING,
|
|
198
|
+
"coverage": "partial",
|
|
199
|
+
"mutations": prior + fresh,
|
|
200
|
+
}
|
|
201
|
+
written = Path("data/initial.json")
|
|
202
|
+
payload = (json.dumps(batch, sort_keys=True, indent=1) + "\n").encode("utf-8")
|
|
203
|
+
from .snapshot import build_default, default_snapshot, sidecar_path
|
|
204
|
+
|
|
205
|
+
with tempfile.TemporaryDirectory(prefix="gateway-import-") as scratch:
|
|
206
|
+
staged = Path(scratch) / "world"
|
|
207
|
+
shutil.copytree(root, staged, ignore=shutil.ignore_patterns(*SCRATCH_IGNORE))
|
|
208
|
+
(staged / written).parent.mkdir(parents=True, exist_ok=True)
|
|
209
|
+
(staged / written).write_bytes(payload)
|
|
210
|
+
# Building the staged snapshot is the proof: every row goes through the store.
|
|
211
|
+
counts = build_default(staged, compile_bundle(staged, snapshots=False))["entityCounts"]
|
|
212
|
+
(root / written).parent.mkdir(parents=True, exist_ok=True)
|
|
213
|
+
(root / written).write_bytes(payload)
|
|
214
|
+
# The proven snapshot moves into the world; compile finds it current and only
|
|
215
|
+
# rebuilds the scenario snapshots on top of it.
|
|
216
|
+
target = default_snapshot(root)
|
|
217
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
218
|
+
shutil.move(str(default_snapshot(staged)), str(target))
|
|
219
|
+
shutil.move(str(sidecar_path(default_snapshot(staged))), str(sidecar_path(target)))
|
|
220
|
+
compile_bundle(root)
|
|
221
|
+
check_bundle(root)
|
|
222
|
+
return {"kind": KIND_SCHEMA, "batch": batch["id"], "entity_counts": counts, "changed": True}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _held(contract: Any, mutations: list[dict]) -> dict[tuple[str, tuple], dict]:
|
|
226
|
+
"""What the batch journal leaves in each entity, keyed the way the store keys it."""
|
|
227
|
+
held: dict[tuple[str, tuple], dict] = {}
|
|
228
|
+
for mutation in mutations:
|
|
229
|
+
entity = mutation.get("entity")
|
|
230
|
+
if mutation.get("op") == "delete" and isinstance(mutation.get("key"), dict):
|
|
231
|
+
key = _key(contract, entity, mutation["key"])
|
|
232
|
+
if key is not None:
|
|
233
|
+
held.pop((entity, key), None)
|
|
234
|
+
continue
|
|
235
|
+
if mutation.get("op") not in ("insert", "update"):
|
|
236
|
+
continue
|
|
237
|
+
value = mutation.get("value")
|
|
238
|
+
if mutation["op"] == "update" and isinstance(mutation.get("key"), dict):
|
|
239
|
+
key = _key(contract, entity, mutation["key"])
|
|
240
|
+
if key is not None and (entity, key) in held and isinstance(value, dict):
|
|
241
|
+
held[(entity, key)] = {**held[(entity, key)], **value}
|
|
242
|
+
continue
|
|
243
|
+
if isinstance(value, dict):
|
|
244
|
+
key = _key(contract, entity, value)
|
|
245
|
+
if key is not None:
|
|
246
|
+
held[(entity, key)] = value
|
|
247
|
+
return held
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _key(contract: Any, entity: str, row: dict) -> tuple | None:
|
|
251
|
+
fields = contract.entities.get(entity, {}).get("primary_key", [])
|
|
252
|
+
if not fields or any(field not in row for field in fields):
|
|
253
|
+
return None
|
|
254
|
+
return tuple(json.dumps(row[field], sort_keys=True) for field in fields)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _plan(contract: Any, rows: Rows, mode: str, held: dict[tuple[str, tuple], dict]) -> list[dict]:
|
|
258
|
+
"""The mutations that take the world from ``held`` to ``held`` + ``rows``.
|
|
259
|
+
|
|
260
|
+
``append`` inserts what is new and updates what changed at an existing key;
|
|
261
|
+
``replace`` first deletes every held row of the named entities that the
|
|
262
|
+
rows do not carry, so replacing an entity with what it already holds is a
|
|
263
|
+
no-op. A row that lacks a primary-key field is refused here, naming the
|
|
264
|
+
field, before the store sees it.
|
|
265
|
+
"""
|
|
266
|
+
mutations: list[dict] = []
|
|
267
|
+
if mode == "replace":
|
|
268
|
+
incoming = {
|
|
269
|
+
(entity, _key(contract, entity, row))
|
|
270
|
+
for entity, listed in rows.items()
|
|
271
|
+
if entity in contract.entities
|
|
272
|
+
for row in listed
|
|
273
|
+
if isinstance(row, dict)
|
|
274
|
+
}
|
|
275
|
+
for (entity, key), value in list(held.items()):
|
|
276
|
+
if entity in rows and (entity, key) not in incoming:
|
|
277
|
+
fields = contract.entities[entity]["primary_key"]
|
|
278
|
+
mutations.append(
|
|
279
|
+
{
|
|
280
|
+
"op": "delete",
|
|
281
|
+
"entity": entity,
|
|
282
|
+
"key": {field: value[field] for field in fields},
|
|
283
|
+
"source": {"record": f"data-import:{entity}:replace"},
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
del held[(entity, key)]
|
|
287
|
+
for entity, listed in rows.items():
|
|
288
|
+
if entity not in contract.entities:
|
|
289
|
+
fail(
|
|
290
|
+
"entity", f"Unknown entity {entity}; the world has {', '.join(sorted(contract.entities))}", f"/{entity}"
|
|
291
|
+
)
|
|
292
|
+
fields = contract.entities[entity]["primary_key"]
|
|
293
|
+
for index, row in enumerate(listed):
|
|
294
|
+
if not isinstance(row, dict):
|
|
295
|
+
fail("rows", "A row must be an object", f"/{entity}/{index}")
|
|
296
|
+
missing = [field for field in fields if field not in row]
|
|
297
|
+
if missing:
|
|
298
|
+
fail("primary_key", f"Row is missing primary key field {missing[0]!r}", f"/{entity}/{index}")
|
|
299
|
+
key = _key(contract, entity, row)
|
|
300
|
+
assert key is not None
|
|
301
|
+
existing = held.get((entity, key))
|
|
302
|
+
if existing == row:
|
|
303
|
+
continue
|
|
304
|
+
identity = hashlib.sha256(json.dumps(row, sort_keys=True).encode()).hexdigest()[:16]
|
|
305
|
+
mutation = {
|
|
306
|
+
"op": "update" if existing is not None else "insert",
|
|
307
|
+
"entity": entity,
|
|
308
|
+
"value": row,
|
|
309
|
+
"source": {"record": f"data-import:{entity}:{identity}"},
|
|
310
|
+
}
|
|
311
|
+
if existing is not None:
|
|
312
|
+
mutation["key"] = {field: row[field] for field in fields}
|
|
313
|
+
mutations.append(mutation)
|
|
314
|
+
held[(entity, key)] = row
|
|
315
|
+
return mutations
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def seed_batch(store: Any, contract: Any, rows: Rows, mode: str = "append", *, batch_id: str | None = None) -> dict:
|
|
319
|
+
"""The batch that seeds a *live* store with ``rows``; apply it with ``store.apply_batch``.
|
|
320
|
+
|
|
321
|
+
The store answers atomically, so a refused row leaves the session as it was.
|
|
322
|
+
``store`` needs only ``list(entity)``; the contract supplies the keys.
|
|
323
|
+
"""
|
|
324
|
+
if mode not in MODES:
|
|
325
|
+
fail("mode", f"mode must be one of {', '.join(MODES)}", "/mode")
|
|
326
|
+
if not isinstance(rows, dict) or not all(isinstance(v, list) for v in rows.values()):
|
|
327
|
+
fail("rows", "rows must map entity names to arrays of rows", "/rows")
|
|
328
|
+
held: dict[tuple[str, tuple], dict] = {}
|
|
329
|
+
for entity, listed in rows.items():
|
|
330
|
+
if entity not in contract.entities:
|
|
331
|
+
continue
|
|
332
|
+
if mode == "replace":
|
|
333
|
+
# Every held row of a replaced entity goes; the plan needs them all.
|
|
334
|
+
for value in store.list(entity):
|
|
335
|
+
key = _key(contract, entity, value)
|
|
336
|
+
if key is not None:
|
|
337
|
+
held[(entity, key)] = value
|
|
338
|
+
continue
|
|
339
|
+
# An append only needs the rows at the keys it names, however large the entity.
|
|
340
|
+
fields = contract.entities[entity]["primary_key"]
|
|
341
|
+
for row in listed:
|
|
342
|
+
if not isinstance(row, dict) or any(field not in row for field in fields):
|
|
343
|
+
continue
|
|
344
|
+
key = _key(contract, entity, row)
|
|
345
|
+
if key is None or (entity, key) in held:
|
|
346
|
+
continue
|
|
347
|
+
try:
|
|
348
|
+
value = store.get(entity, {field: row[field] for field in fields})
|
|
349
|
+
except ContractError:
|
|
350
|
+
continue # a key the contract refuses is refused by the plan, with the field named
|
|
351
|
+
if value is not None:
|
|
352
|
+
held[(entity, key)] = value
|
|
353
|
+
mutations = _plan(contract, rows, mode, held)
|
|
354
|
+
return {
|
|
355
|
+
"id": batch_id or f"seed-{uuid.uuid4().hex}",
|
|
356
|
+
"target": contract.target,
|
|
357
|
+
"mapping": MAPPING,
|
|
358
|
+
"coverage": "partial",
|
|
359
|
+
"mutations": mutations,
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
# ---------------------------------------------------------------- mock-tools
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _import_mock_tools(root: Path, rows: Rows, mode: str) -> dict:
|
|
367
|
+
schema = root / "db/schema.sql"
|
|
368
|
+
if not schema.is_file():
|
|
369
|
+
fail("schema", "mock-tools world has no db/schema.sql to read tables from", "db/schema.sql")
|
|
370
|
+
with sqlite3.connect(":memory:") as probe:
|
|
371
|
+
try:
|
|
372
|
+
probe.executescript(schema.read_text(encoding="utf-8"))
|
|
373
|
+
except sqlite3.Error as error:
|
|
374
|
+
fail("schema", f"db/schema.sql does not load: {error}", "db/schema.sql")
|
|
375
|
+
columns = sqlite_columns(probe)
|
|
376
|
+
statements = sqlite_statements(columns, rows, mode)
|
|
377
|
+
if not statements:
|
|
378
|
+
return {"kind": KIND_MOCK_TOOLS, "entity_counts": entity_counts(root), "changed": False}
|
|
379
|
+
seed = root / "db/seed.sql"
|
|
380
|
+
existing = seed.read_text(encoding="utf-8") if seed.is_file() else ""
|
|
381
|
+
if existing and not existing.endswith("\n"):
|
|
382
|
+
existing += "\n"
|
|
383
|
+
appended = existing + "\n".join(statements) + "\n"
|
|
384
|
+
from .._materialize import materialize_template
|
|
385
|
+
|
|
386
|
+
with tempfile.TemporaryDirectory(prefix="gateway-import-") as scratch:
|
|
387
|
+
staged = Path(scratch) / "world"
|
|
388
|
+
shutil.copytree(root, staged, ignore=shutil.ignore_patterns(*SCRATCH_IGNORE))
|
|
389
|
+
(staged / "db/seed.sql").write_text(appended, encoding="utf-8")
|
|
390
|
+
database = Path(scratch) / "world.db"
|
|
391
|
+
materialize_template(staged, database, require_sql=True)
|
|
392
|
+
counts = _sqlite_counts(database)
|
|
393
|
+
seed.write_text(appended, encoding="utf-8")
|
|
394
|
+
return {"kind": KIND_MOCK_TOOLS, "entity_counts": counts, "changed": True, "statements": len(statements)}
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def sqlite_columns(conn: sqlite3.Connection) -> dict[str, list[str]]:
|
|
398
|
+
"""Every user table's columns, in declaration order."""
|
|
399
|
+
tables = [
|
|
400
|
+
row[0]
|
|
401
|
+
for row in conn.execute("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'")
|
|
402
|
+
]
|
|
403
|
+
return {table: [row[1] for row in conn.execute(f"PRAGMA table_info({sql_ident(table)})")] for table in tables}
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def sql_ident(name: str) -> str:
|
|
407
|
+
"""A SQLite identifier, quoted: a table or column named by a schema is a
|
|
408
|
+
name, never SQL, whatever characters it carries."""
|
|
409
|
+
return '"' + name.replace('"', '""') + '"'
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _check_sqlite_rows(columns: dict[str, list[str]], rows: Rows) -> None:
|
|
413
|
+
for table, listed in rows.items():
|
|
414
|
+
if table not in columns:
|
|
415
|
+
fail(
|
|
416
|
+
"table",
|
|
417
|
+
f"No table {table} in db/schema.sql; tables: {', '.join(sorted(columns)) or 'none'}",
|
|
418
|
+
f"/{table}",
|
|
419
|
+
)
|
|
420
|
+
for index, row in enumerate(listed):
|
|
421
|
+
if not isinstance(row, dict):
|
|
422
|
+
fail("rows", "A row must be an object", f"/{table}/{index}")
|
|
423
|
+
for column, value in row.items():
|
|
424
|
+
if column not in columns[table]:
|
|
425
|
+
fail(
|
|
426
|
+
"column",
|
|
427
|
+
f"Column {column} is not in table {table}; columns: {', '.join(columns[table])}",
|
|
428
|
+
f"/{table}/{index}/{column}",
|
|
429
|
+
)
|
|
430
|
+
if not isinstance(value, (str, int, float, bool, type(None))):
|
|
431
|
+
fail(
|
|
432
|
+
"value",
|
|
433
|
+
f"Column {column} of {table} takes a scalar; encode nested values as text yourself",
|
|
434
|
+
f"/{table}/{index}/{column}",
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def sqlite_statements(columns: dict[str, list[str]], rows: Rows, mode: str = "append") -> list[str]:
|
|
439
|
+
"""SQL text that seeds ``rows`` into tables with these ``columns``.
|
|
440
|
+
|
|
441
|
+
``append`` writes ``INSERT OR REPLACE`` (an upsert by the table's own
|
|
442
|
+
primary key); ``replace`` prefixes ``DELETE FROM`` for each named table and
|
|
443
|
+
writes plain inserts.
|
|
444
|
+
"""
|
|
445
|
+
if mode not in MODES:
|
|
446
|
+
fail("mode", f"mode must be one of {', '.join(MODES)}", "/mode")
|
|
447
|
+
_check_sqlite_rows(columns, rows)
|
|
448
|
+
statements: list[str] = []
|
|
449
|
+
verb = "INSERT" if mode == "replace" else "INSERT OR REPLACE"
|
|
450
|
+
for table, listed in rows.items():
|
|
451
|
+
if mode == "replace":
|
|
452
|
+
statements.append(f"DELETE FROM {sql_ident(table)};")
|
|
453
|
+
for row in listed:
|
|
454
|
+
if not row:
|
|
455
|
+
statements.append(f"{verb} INTO {sql_ident(table)} DEFAULT VALUES;")
|
|
456
|
+
continue
|
|
457
|
+
names = ", ".join(sql_ident(column) for column in row)
|
|
458
|
+
values = ", ".join(_sql_literal(value) for value in row.values())
|
|
459
|
+
statements.append(f"{verb} INTO {sql_ident(table)} ({names}) VALUES ({values});")
|
|
460
|
+
return statements
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def _sql_literal(value: Any) -> str:
|
|
464
|
+
if value is None:
|
|
465
|
+
return "NULL"
|
|
466
|
+
if isinstance(value, bool):
|
|
467
|
+
return "1" if value else "0"
|
|
468
|
+
if isinstance(value, (int, float)):
|
|
469
|
+
return repr(value)
|
|
470
|
+
return "'" + str(value).replace("'", "''") + "'"
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def seed_sqlite(conn: sqlite3.Connection, rows: Rows, mode: str = "append") -> dict[str, int]:
|
|
474
|
+
"""Execute the seed for ``rows`` against a live sqlite, in one transaction.
|
|
475
|
+
|
|
476
|
+
Columns come from the live database itself. A refused row rolls the whole
|
|
477
|
+
seed back. Returns the row count of every touched table afterwards.
|
|
478
|
+
"""
|
|
479
|
+
statements = sqlite_statements(sqlite_columns(conn), rows, mode)
|
|
480
|
+
try:
|
|
481
|
+
conn.execute("BEGIN")
|
|
482
|
+
for statement in statements:
|
|
483
|
+
conn.execute(statement)
|
|
484
|
+
conn.commit()
|
|
485
|
+
except sqlite3.Error as error:
|
|
486
|
+
conn.rollback()
|
|
487
|
+
fail("sqlite", f"seed refused by the database: {error}", "/rows")
|
|
488
|
+
return {table: conn.execute(f"SELECT COUNT(*) FROM {sql_ident(table)}").fetchone()[0] for table in rows}
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def _sqlite_counts(database: Path) -> dict[str, int]:
|
|
492
|
+
conn = sqlite3.connect(database)
|
|
493
|
+
try:
|
|
494
|
+
return {
|
|
495
|
+
table: conn.execute(f"SELECT COUNT(*) FROM {sql_ident(table)}").fetchone()[0]
|
|
496
|
+
for table in sqlite_columns(conn)
|
|
497
|
+
}
|
|
498
|
+
finally:
|
|
499
|
+
conn.close()
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
# ---------------------------------------------------------------- show
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def entity_counts(root: str | Path) -> dict[str, int]:
|
|
506
|
+
"""How many rows each entity (or table) of the world at ``root`` seeds with."""
|
|
507
|
+
root = Path(root)
|
|
508
|
+
kind = world_kind(root)
|
|
509
|
+
if kind == KIND_API:
|
|
510
|
+
base = root / "seed/base.json"
|
|
511
|
+
seed = read_json(base) if base.is_file() else {}
|
|
512
|
+
return {entity: len(listed) for entity, listed in seed.items() if isinstance(listed, list)}
|
|
513
|
+
if kind not in (KIND_SCHEMA, KIND_MOCK_TOOLS):
|
|
514
|
+
_refuse_kind(kind, root)
|
|
515
|
+
if kind == KIND_SCHEMA:
|
|
516
|
+
from .snapshot import current_default
|
|
517
|
+
|
|
518
|
+
contract = check_bundle(root)
|
|
519
|
+
snapshot = current_default(root, contract)
|
|
520
|
+
if snapshot is not None:
|
|
521
|
+
return dict(snapshot["entityCounts"])
|
|
522
|
+
from .._materialize import materialize_template
|
|
523
|
+
|
|
524
|
+
with tempfile.TemporaryDirectory(prefix="gateway-show-") as scratch:
|
|
525
|
+
database = Path(scratch) / "world.db"
|
|
526
|
+
materialize_template(root, database, require_sql=kind == KIND_MOCK_TOOLS)
|
|
527
|
+
if kind == KIND_SCHEMA:
|
|
528
|
+
with SQLiteStore.open(database, contract) as store:
|
|
529
|
+
return store.counts()
|
|
530
|
+
return _sqlite_counts(database)
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def entity_rows(root: str | Path, entity: str) -> list[dict]:
|
|
534
|
+
"""The seeded rows of one entity (or table) of the world at ``root``."""
|
|
535
|
+
root = Path(root)
|
|
536
|
+
kind = world_kind(root)
|
|
537
|
+
if kind == KIND_API:
|
|
538
|
+
base = root / "seed/base.json"
|
|
539
|
+
seed = read_json(base) if base.is_file() else {}
|
|
540
|
+
if entity not in seed:
|
|
541
|
+
fail("entity", f"Unknown entity {entity}; the seed has {', '.join(sorted(seed)) or 'none'}", f"/{entity}")
|
|
542
|
+
return list(seed[entity])
|
|
543
|
+
if kind not in (KIND_SCHEMA, KIND_MOCK_TOOLS):
|
|
544
|
+
_refuse_kind(kind, root)
|
|
545
|
+
if kind == KIND_SCHEMA:
|
|
546
|
+
from .snapshot import current_default, default_snapshot
|
|
547
|
+
|
|
548
|
+
contract = check_bundle(root)
|
|
549
|
+
if entity not in contract.entities:
|
|
550
|
+
fail(
|
|
551
|
+
"entity",
|
|
552
|
+
f"Unknown entity {entity}; the world has {', '.join(sorted(contract.entities))}",
|
|
553
|
+
f"/{entity}",
|
|
554
|
+
)
|
|
555
|
+
if current_default(root, contract) is not None:
|
|
556
|
+
# Reads only; the compiled snapshot answers in place.
|
|
557
|
+
with SQLiteStore.open(default_snapshot(root), contract, audit=False) as store:
|
|
558
|
+
return list(store.list(entity))
|
|
559
|
+
from .._materialize import materialize_template
|
|
560
|
+
|
|
561
|
+
with tempfile.TemporaryDirectory(prefix="gateway-show-") as scratch:
|
|
562
|
+
database = Path(scratch) / "world.db"
|
|
563
|
+
materialize_template(root, database, require_sql=kind == KIND_MOCK_TOOLS)
|
|
564
|
+
if kind == KIND_SCHEMA:
|
|
565
|
+
with SQLiteStore.open(database, contract) as store:
|
|
566
|
+
return list(store.list(entity))
|
|
567
|
+
conn = sqlite3.connect(database)
|
|
568
|
+
try:
|
|
569
|
+
columns = sqlite_columns(conn)
|
|
570
|
+
if entity not in columns:
|
|
571
|
+
fail("table", f"No table {entity}; tables: {', '.join(sorted(columns)) or 'none'}", f"/{entity}")
|
|
572
|
+
conn.row_factory = sqlite3.Row
|
|
573
|
+
return [dict(row) for row in conn.execute(f"SELECT * FROM {sql_ident(entity)}")]
|
|
574
|
+
finally:
|
|
575
|
+
conn.close()
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
__all__ = [
|
|
579
|
+
"ContractError",
|
|
580
|
+
"INLINE_BYTES",
|
|
581
|
+
"INLINE_ROWS",
|
|
582
|
+
"MERGES",
|
|
583
|
+
"MODES",
|
|
584
|
+
"entity_counts",
|
|
585
|
+
"entity_rows",
|
|
586
|
+
"import_rows",
|
|
587
|
+
"parse_rows",
|
|
588
|
+
"seed_batch",
|
|
589
|
+
"seed_sqlite",
|
|
590
|
+
"sqlite_columns",
|
|
591
|
+
"sqlite_statements",
|
|
592
|
+
"world_kind",
|
|
593
|
+
]
|