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.
Files changed (376) hide show
  1. gatewaysdk/__init__.py +540 -0
  2. gatewaysdk/adapter/__init__.py +15 -0
  3. gatewaysdk/adapter/base.py +94 -0
  4. gatewaysdk/adapter/messages.py +270 -0
  5. gatewaysdk/adapter/triplet.py +1026 -0
  6. gatewaysdk/algorithm/__init__.py +39 -0
  7. gatewaysdk/algorithm/apo/__init__.py +5 -0
  8. gatewaysdk/algorithm/apo/apo.py +898 -0
  9. gatewaysdk/algorithm/apo/prompts/apply_edit_variant01.poml +22 -0
  10. gatewaysdk/algorithm/apo/prompts/apply_edit_variant02.poml +18 -0
  11. gatewaysdk/algorithm/apo/prompts/text_gradient_variant01.poml +18 -0
  12. gatewaysdk/algorithm/apo/prompts/text_gradient_variant02.poml +16 -0
  13. gatewaysdk/algorithm/apo/prompts/text_gradient_variant03.poml +107 -0
  14. gatewaysdk/algorithm/base.py +262 -0
  15. gatewaysdk/algorithm/decorator.py +264 -0
  16. gatewaysdk/algorithm/evals/__init__.py +7 -0
  17. gatewaysdk/algorithm/evals/evals.py +217 -0
  18. gatewaysdk/algorithm/fast.py +250 -0
  19. gatewaysdk/algorithm/gepa/__init__.py +61 -0
  20. gatewaysdk/algorithm/gepa/adapter.py +495 -0
  21. gatewaysdk/algorithm/gepa/gepa.py +570 -0
  22. gatewaysdk/algorithm/gepa/lib/__init__.py +18 -0
  23. gatewaysdk/algorithm/gepa/lib/adapters/README.md +12 -0
  24. gatewaysdk/algorithm/gepa/lib/adapters/__init__.py +0 -0
  25. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/README.md +341 -0
  26. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/__init__.py +1 -0
  27. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
  28. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/requirements.txt +1 -0
  29. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/README.md +0 -0
  30. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/__init__.py +0 -0
  31. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/default_adapter.py +209 -0
  32. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/README.md +7 -0
  33. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/__init__.py +0 -0
  34. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/dspy_adapter.py +307 -0
  35. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/README.md +99 -0
  36. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
  37. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/full_program_adapter.py +268 -0
  38. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/GEPA_RAG.md +621 -0
  39. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/__init__.py +56 -0
  40. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
  41. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
  42. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
  43. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
  44. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
  45. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
  46. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
  47. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
  48. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
  49. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
  50. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/README.md +552 -0
  51. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/__init__.py +37 -0
  52. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_adapter.py +699 -0
  53. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_client.py +364 -0
  54. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/README.md +9 -0
  55. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/__init__.py +0 -0
  56. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
  57. gatewaysdk/algorithm/gepa/lib/api.py +382 -0
  58. gatewaysdk/algorithm/gepa/lib/core/__init__.py +0 -0
  59. gatewaysdk/algorithm/gepa/lib/core/adapter.py +180 -0
  60. gatewaysdk/algorithm/gepa/lib/core/data_loader.py +74 -0
  61. gatewaysdk/algorithm/gepa/lib/core/engine.py +379 -0
  62. gatewaysdk/algorithm/gepa/lib/core/result.py +233 -0
  63. gatewaysdk/algorithm/gepa/lib/core/state.py +636 -0
  64. gatewaysdk/algorithm/gepa/lib/examples/__init__.py +0 -0
  65. gatewaysdk/algorithm/gepa/lib/examples/aime.py +24 -0
  66. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/eval_default.py +111 -0
  67. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/instruction_prompt.txt +9 -0
  68. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/optimal_prompt.txt +24 -0
  69. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/train_anymaths.py +177 -0
  70. gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/arc_agi.ipynb +25705 -0
  71. gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/example.ipynb +348 -0
  72. gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/__init__.py +4 -0
  73. gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/mcp_optimization_example.py +456 -0
  74. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/RAG_GUIDE.md +613 -0
  75. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/__init__.py +9 -0
  76. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/rag_optimization.py +820 -0
  77. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/requirements-rag.txt +29 -0
  78. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/instruction_prompt.txt +16 -0
  79. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/terminus.txt +9 -0
  80. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/train_terminus.py +161 -0
  81. gatewaysdk/algorithm/gepa/lib/gepa_utils.py +117 -0
  82. gatewaysdk/algorithm/gepa/lib/logging/__init__.py +0 -0
  83. gatewaysdk/algorithm/gepa/lib/logging/experiment_tracker.py +187 -0
  84. gatewaysdk/algorithm/gepa/lib/logging/logger.py +75 -0
  85. gatewaysdk/algorithm/gepa/lib/logging/utils.py +103 -0
  86. gatewaysdk/algorithm/gepa/lib/proposer/__init__.py +0 -0
  87. gatewaysdk/algorithm/gepa/lib/proposer/base.py +31 -0
  88. gatewaysdk/algorithm/gepa/lib/proposer/merge.py +357 -0
  89. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/__init__.py +0 -0
  90. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/base.py +49 -0
  91. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/reflective_mutation.py +176 -0
  92. gatewaysdk/algorithm/gepa/lib/py.typed +0 -0
  93. gatewaysdk/algorithm/gepa/lib/strategies/__init__.py +0 -0
  94. gatewaysdk/algorithm/gepa/lib/strategies/batch_sampler.py +77 -0
  95. gatewaysdk/algorithm/gepa/lib/strategies/candidate_selector.py +50 -0
  96. gatewaysdk/algorithm/gepa/lib/strategies/component_selector.py +36 -0
  97. gatewaysdk/algorithm/gepa/lib/strategies/eval_policy.py +64 -0
  98. gatewaysdk/algorithm/gepa/lib/strategies/instruction_proposal.py +126 -0
  99. gatewaysdk/algorithm/gepa/lib/utils/__init__.py +10 -0
  100. gatewaysdk/algorithm/gepa/lib/utils/stop_condition.py +196 -0
  101. gatewaysdk/algorithm/gepa/tracing.py +105 -0
  102. gatewaysdk/algorithm/utils.py +177 -0
  103. gatewaysdk/algorithm/verl/__init__.py +5 -0
  104. gatewaysdk/algorithm/verl/interface.py +202 -0
  105. gatewaysdk/automations.py +111 -0
  106. gatewaysdk/benchmark_hub/README.md +88 -0
  107. gatewaysdk/benchmark_hub/__init__.py +119 -0
  108. gatewaysdk/benchmark_hub/_archive.py +189 -0
  109. gatewaysdk/benchmark_hub/_tar.py +6 -0
  110. gatewaysdk/benchmark_hub/client.py +992 -0
  111. gatewaysdk/benchmark_hub/dispatch_shard.py +34 -0
  112. gatewaysdk/benchmark_hub/eval_config.py +20 -0
  113. gatewaysdk/benchmark_hub/evals.py +351 -0
  114. gatewaysdk/benchmark_hub/harbor_adapter.py +673 -0
  115. gatewaysdk/benchmark_hub/path_utils.py +81 -0
  116. gatewaysdk/benchmark_hub/save_utils.py +101 -0
  117. gatewaysdk/benchmark_hub/verifiers_adapter.py +327 -0
  118. gatewaysdk/benchmark_hub/versioning.py +72 -0
  119. gatewaysdk/build.py +515 -0
  120. gatewaysdk/cli/__init__.py +58 -0
  121. gatewaysdk/cli/agent_runner.py +132 -0
  122. gatewaysdk/cli/http_client.py +115 -0
  123. gatewaysdk/cli/platform.py +4086 -0
  124. gatewaysdk/cli/prometheus.py +115 -0
  125. gatewaysdk/cli/release_gate.py +215 -0
  126. gatewaysdk/cli/store.py +131 -0
  127. gatewaysdk/cli/vllm.py +29 -0
  128. gatewaysdk/client.py +406 -0
  129. gatewaysdk/config.py +348 -0
  130. gatewaysdk/connectors/__init__.py +25 -0
  131. gatewaysdk/connectors/client.py +203 -0
  132. gatewaysdk/connectors/skill.py +25 -0
  133. gatewaysdk/connectors/template.py +106 -0
  134. gatewaysdk/context.py +606 -0
  135. gatewaysdk/emitter/__init__.py +43 -0
  136. gatewaysdk/emitter/annotation.py +370 -0
  137. gatewaysdk/emitter/exception.py +54 -0
  138. gatewaysdk/emitter/message.py +61 -0
  139. gatewaysdk/emitter/object.py +117 -0
  140. gatewaysdk/emitter/reward.py +320 -0
  141. gatewaysdk/env_var.py +156 -0
  142. gatewaysdk/environment/__init__.py +108 -0
  143. gatewaysdk/environment/_bundle.py +281 -0
  144. gatewaysdk/environment/_harbor.py +276 -0
  145. gatewaysdk/environment/_materialize.py +97 -0
  146. gatewaysdk/environment/_tar.py +33 -0
  147. gatewaysdk/environment/_world.py +346 -0
  148. gatewaysdk/environment/core.py +527 -0
  149. gatewaysdk/environment/errors.py +58 -0
  150. gatewaysdk/environment/runtime.py +150 -0
  151. gatewaysdk/environment/schema/__init__.py +35 -0
  152. gatewaysdk/environment/schema/__main__.py +225 -0
  153. gatewaysdk/environment/schema/_slack_fidelity.py +116 -0
  154. gatewaysdk/environment/schema/_slack_scenario.py +183 -0
  155. gatewaysdk/environment/schema/api.py +1628 -0
  156. gatewaysdk/environment/schema/batch.py +755 -0
  157. gatewaysdk/environment/schema/compiler.py +615 -0
  158. gatewaysdk/environment/schema/conform.py +428 -0
  159. gatewaysdk/environment/schema/connector.py +777 -0
  160. gatewaysdk/environment/schema/connectors/apple-business-manager/handlers.py +17 -0
  161. gatewaysdk/environment/schema/connectors/apple-business-manager/parity.json +28 -0
  162. gatewaysdk/environment/schema/connectors/apple-business-manager/provenance.json +233 -0
  163. gatewaysdk/environment/schema/connectors/apple-business-manager/scope.toml +112 -0
  164. gatewaysdk/environment/schema/connectors/apple-business-manager/world.json +1380 -0
  165. gatewaysdk/environment/schema/connectors/base.json +28 -0
  166. gatewaysdk/environment/schema/connectors/custom/handlers.py +18 -0
  167. gatewaysdk/environment/schema/connectors/custom/world.json +123 -0
  168. gatewaysdk/environment/schema/connectors/github/handlers.py +81 -0
  169. gatewaysdk/environment/schema/connectors/github/parity.json +37 -0
  170. gatewaysdk/environment/schema/connectors/github/provenance.json +134 -0
  171. gatewaysdk/environment/schema/connectors/github/scope.toml +158 -0
  172. gatewaysdk/environment/schema/connectors/github/world.json +5869 -0
  173. gatewaysdk/environment/schema/connectors/google-calendar/handlers.py +23 -0
  174. gatewaysdk/environment/schema/connectors/google-calendar/world.json +209 -0
  175. gatewaysdk/environment/schema/connectors/google-drive/handlers.py +18 -0
  176. gatewaysdk/environment/schema/connectors/google-drive/world.json +286 -0
  177. gatewaysdk/environment/schema/connectors/jamf/handlers.py +34 -0
  178. gatewaysdk/environment/schema/connectors/jamf/parity.json +49 -0
  179. gatewaysdk/environment/schema/connectors/jamf/provenance.json +181 -0
  180. gatewaysdk/environment/schema/connectors/jamf/scope.toml +128 -0
  181. gatewaysdk/environment/schema/connectors/jamf/world.json +2218 -0
  182. gatewaysdk/environment/schema/connectors/jira/handlers.py +28 -0
  183. gatewaysdk/environment/schema/connectors/jira/world.json +389 -0
  184. gatewaysdk/environment/schema/connectors/kandji/handlers.py +38 -0
  185. gatewaysdk/environment/schema/connectors/kandji/parity.json +29 -0
  186. gatewaysdk/environment/schema/connectors/kandji/provenance.json +152 -0
  187. gatewaysdk/environment/schema/connectors/kandji/scope.toml +120 -0
  188. gatewaysdk/environment/schema/connectors/kandji/world.json +765 -0
  189. gatewaysdk/environment/schema/connectors/linear/handlers.py +20 -0
  190. gatewaysdk/environment/schema/connectors/linear/world.json +363 -0
  191. gatewaysdk/environment/schema/connectors/microsoft-teams/fidelity.json +45 -0
  192. gatewaysdk/environment/schema/connectors/microsoft-teams/handlers.py +1 -0
  193. gatewaysdk/environment/schema/connectors/microsoft-teams/provenance.json +851 -0
  194. gatewaysdk/environment/schema/connectors/microsoft-teams/scope.md +231 -0
  195. gatewaysdk/environment/schema/connectors/microsoft-teams/teams-types.json +1710 -0
  196. gatewaysdk/environment/schema/connectors/microsoft-teams/world.json +858 -0
  197. gatewaysdk/environment/schema/connectors/netsuite/handlers.py +32 -0
  198. gatewaysdk/environment/schema/connectors/netsuite/parity.json +50 -0
  199. gatewaysdk/environment/schema/connectors/netsuite/provenance.json +137 -0
  200. gatewaysdk/environment/schema/connectors/netsuite/scope.toml +78 -0
  201. gatewaysdk/environment/schema/connectors/netsuite/world.json +16243 -0
  202. gatewaysdk/environment/schema/connectors/salesforce/handlers.py +806 -0
  203. gatewaysdk/environment/schema/connectors/salesforce/parity.json +46 -0
  204. gatewaysdk/environment/schema/connectors/salesforce/provenance.json +143 -0
  205. gatewaysdk/environment/schema/connectors/salesforce/scope.toml +174 -0
  206. gatewaysdk/environment/schema/connectors/salesforce/world.json +4695 -0
  207. gatewaysdk/environment/schema/connectors/slack/MODEL.md +236 -0
  208. gatewaysdk/environment/schema/connectors/slack/capabilities.json +1039 -0
  209. gatewaysdk/environment/schema/connectors/slack/handlers.py +762 -0
  210. gatewaysdk/environment/schema/connectors/slack/parity.json +65 -0
  211. gatewaysdk/environment/schema/connectors/slack/provenance.json +294 -0
  212. gatewaysdk/environment/schema/connectors/slack/scope.toml +44 -0
  213. gatewaysdk/environment/schema/connectors/slack/world.json +4993 -0
  214. gatewaysdk/environment/schema/connectors/workday/handlers.py +75 -0
  215. gatewaysdk/environment/schema/connectors/workday/parity.json +36 -0
  216. gatewaysdk/environment/schema/connectors/workday/provenance.json +179 -0
  217. gatewaysdk/environment/schema/connectors/workday/scope.toml +180 -0
  218. gatewaysdk/environment/schema/connectors/workday/world.json +806 -0
  219. gatewaysdk/environment/schema/data.py +593 -0
  220. gatewaysdk/environment/schema/extract.py +263 -0
  221. gatewaysdk/environment/schema/host.py +2437 -0
  222. gatewaysdk/environment/schema/host_surfaces.py +275 -0
  223. gatewaysdk/environment/schema/platform.py +1272 -0
  224. gatewaysdk/environment/schema/scaffold.py +250 -0
  225. gatewaysdk/environment/schema/skill.py +118 -0
  226. gatewaysdk/environment/schema/skills/agents/connector-template-author.md +202 -0
  227. gatewaysdk/environment/schema/skills/connector-schema-authoring/CONNECTORS.md +331 -0
  228. gatewaysdk/environment/schema/skills/connector-schema-authoring/CONVENTIONS.md +100 -0
  229. gatewaysdk/environment/schema/skills/connector-schema-authoring/SKILL.md +313 -0
  230. gatewaysdk/environment/schema/skills/world-data-ingestion/ROWS.md +223 -0
  231. gatewaysdk/environment/schema/skills/world-data-ingestion/SKILL.md +293 -0
  232. gatewaysdk/environment/schema/skills/worlds-getting-started/SKILL.md +390 -0
  233. gatewaysdk/environment/schema/slack.py +757 -0
  234. gatewaysdk/environment/schema/snapshot.py +479 -0
  235. gatewaysdk/environment/schema/store.py +904 -0
  236. gatewaysdk/environment/schema/tasks.py +525 -0
  237. gatewaysdk/environment/schema/tools_world.py +322 -0
  238. gatewaysdk/environment/schema/validation.py +577 -0
  239. gatewaysdk/environment/schema/worker.py +78 -0
  240. gatewaysdk/environment/schema/workers.py +500 -0
  241. gatewaysdk/environment/schema/world_tests.py +336 -0
  242. gatewaysdk/environments/__init__.py +24 -0
  243. gatewaysdk/environments/client.py +486 -0
  244. gatewaysdk/environments/types.py +318 -0
  245. gatewaysdk/execution/__init__.py +15 -0
  246. gatewaysdk/execution/base.py +64 -0
  247. gatewaysdk/execution/client_server.py +443 -0
  248. gatewaysdk/execution/events.py +69 -0
  249. gatewaysdk/execution/inter_process.py +16 -0
  250. gatewaysdk/execution/shared_memory.py +282 -0
  251. gatewaysdk/experiments/__init__.py +90 -0
  252. gatewaysdk/experiments/assignment.py +177 -0
  253. gatewaysdk/experiments/client.py +877 -0
  254. gatewaysdk/experiments/exposure.py +222 -0
  255. gatewaysdk/experiments/types.py +81 -0
  256. gatewaysdk/importers/__init__.py +49 -0
  257. gatewaysdk/importers/_normalize.py +91 -0
  258. gatewaysdk/importers/builder.py +173 -0
  259. gatewaysdk/importers/client.py +95 -0
  260. gatewaysdk/importers/langsmith.py +196 -0
  261. gatewaysdk/importers/recipes.py +249 -0
  262. gatewaysdk/instrumentation/__init__.py +300 -0
  263. gatewaysdk/instrumentation/agentops.py +314 -0
  264. gatewaysdk/instrumentation/agentops_langchain.py +45 -0
  265. gatewaysdk/instrumentation/base.py +119 -0
  266. gatewaysdk/instrumentation/litellm.py +83 -0
  267. gatewaysdk/instrumentation/registry.py +273 -0
  268. gatewaysdk/instrumentation/vllm.py +81 -0
  269. gatewaysdk/instrumentation/weave.py +500 -0
  270. gatewaysdk/integrations/__init__.py +15 -0
  271. gatewaysdk/integrations/gateway/__init__.py +11 -0
  272. gatewaysdk/integrations/gateway/client.py +171 -0
  273. gatewaysdk/integrations/tool_access.py +549 -0
  274. gatewaysdk/litagent/__init__.py +11 -0
  275. gatewaysdk/litagent/decorator.py +536 -0
  276. gatewaysdk/litagent/litagent.py +252 -0
  277. gatewaysdk/llm_proxy.py +1742 -0
  278. gatewaysdk/logging.py +370 -0
  279. gatewaysdk/memory.py +278 -0
  280. gatewaysdk/personas.py +186 -0
  281. gatewaysdk/platform/__init__.py +17 -0
  282. gatewaysdk/platform/builder.py +221 -0
  283. gatewaysdk/platform/compatibility.py +67 -0
  284. gatewaysdk/platform/manifest.py +185 -0
  285. gatewaysdk/platform/orchestrator.py +901 -0
  286. gatewaysdk/platform/registry.py +122 -0
  287. gatewaysdk/platform/worker.py +864 -0
  288. gatewaysdk/replay/__init__.py +1032 -0
  289. gatewaysdk/replay/pytest.py +56 -0
  290. gatewaysdk/reward.py +7 -0
  291. gatewaysdk/run.py +1781 -0
  292. gatewaysdk/runner/__init__.py +11 -0
  293. gatewaysdk/runner/agent.py +878 -0
  294. gatewaysdk/runner/base.py +182 -0
  295. gatewaysdk/runner/legacy.py +309 -0
  296. gatewaysdk/security.py +700 -0
  297. gatewaysdk/semconv.py +170 -0
  298. gatewaysdk/server.py +399 -0
  299. gatewaysdk/sessions.py +282 -0
  300. gatewaysdk/store/__init__.py +45 -0
  301. gatewaysdk/store/base.py +908 -0
  302. gatewaysdk/store/client_server.py +2093 -0
  303. gatewaysdk/store/collection/__init__.py +30 -0
  304. gatewaysdk/store/collection/base.py +587 -0
  305. gatewaysdk/store/collection/memory.py +970 -0
  306. gatewaysdk/store/collection/mongo.py +1412 -0
  307. gatewaysdk/store/collection_based.py +1823 -0
  308. gatewaysdk/store/gateway.py +983 -0
  309. gatewaysdk/store/gateway_listener.py +465 -0
  310. gatewaysdk/store/listener.py +58 -0
  311. gatewaysdk/store/memory.py +396 -0
  312. gatewaysdk/store/mongo.py +165 -0
  313. gatewaysdk/store/redis_stream.py +517 -0
  314. gatewaysdk/store/sqlite.py +3 -0
  315. gatewaysdk/store/threading.py +370 -0
  316. gatewaysdk/store/utils.py +142 -0
  317. gatewaysdk/tracer/__init__.py +14 -0
  318. gatewaysdk/tracer/base.py +286 -0
  319. gatewaysdk/tracer/dummy.py +106 -0
  320. gatewaysdk/tracer/otel.py +559 -0
  321. gatewaysdk/tracing/__init__.py +110 -0
  322. gatewaysdk/tracing/api.py +808 -0
  323. gatewaysdk/tracing/attributes.py +9 -0
  324. gatewaysdk/tracing/context.py +272 -0
  325. gatewaysdk/tracing/exporters/__init__.py +10 -0
  326. gatewaysdk/tracing/exporters/gateway.py +228 -0
  327. gatewaysdk/tracing/identity.py +288 -0
  328. gatewaysdk/tracing/init.py +620 -0
  329. gatewaysdk/tracing/instrumentors/__init__.py +15 -0
  330. gatewaysdk/tracing/instrumentors/claude_agent_sdk.py +766 -0
  331. gatewaysdk/tracing/instrumentors/instrumentation_principles.md +294 -0
  332. gatewaysdk/tracing/instrumentors/registry.py +352 -0
  333. gatewaysdk/tracing/mapping.py +729 -0
  334. gatewaysdk/tracing/processors.py +393 -0
  335. gatewaysdk/tracing/push.py +617 -0
  336. gatewaysdk/tracing/push_models.py +247 -0
  337. gatewaysdk/tracing/semconv.py +294 -0
  338. gatewaysdk/tracing/span_builder.py +356 -0
  339. gatewaysdk/trainer/__init__.py +6 -0
  340. gatewaysdk/trainer/init_utils.py +263 -0
  341. gatewaysdk/trainer/legacy.py +359 -0
  342. gatewaysdk/trainer/registry.py +12 -0
  343. gatewaysdk/trainer/trainer.py +638 -0
  344. gatewaysdk/types/__init__.py +63 -0
  345. gatewaysdk/types/core.py +556 -0
  346. gatewaysdk/types/resources.py +204 -0
  347. gatewaysdk/types/tracer.py +515 -0
  348. gatewaysdk/types/tracing.py +162 -0
  349. gatewaysdk/users.py +251 -0
  350. gatewaysdk/utils/__init__.py +1 -0
  351. gatewaysdk/utils/id.py +18 -0
  352. gatewaysdk/utils/metrics.py +1025 -0
  353. gatewaysdk/utils/otel.py +550 -0
  354. gatewaysdk/utils/otlp.py +556 -0
  355. gatewaysdk/utils/redact.py +22 -0
  356. gatewaysdk/utils/server_launcher.py +1045 -0
  357. gatewaysdk/utils/system_snapshot.py +90 -0
  358. gatewaysdk/verl/__init__.py +8 -0
  359. gatewaysdk/verl/__main__.py +6 -0
  360. gatewaysdk/verl/async_server.py +46 -0
  361. gatewaysdk/verl/config.yaml +27 -0
  362. gatewaysdk/verl/daemon.py +1154 -0
  363. gatewaysdk/verl/dataset.py +44 -0
  364. gatewaysdk/verl/entrypoint.py +248 -0
  365. gatewaysdk/verl/trainer.py +549 -0
  366. gatewaysdk/world_browser.py +748 -0
  367. gatewaysdk/world_data.py +453 -0
  368. gatewaysdk/world_sessions.py +978 -0
  369. gatewaysdk/world_tasks.py +285 -0
  370. gatewaysdk/world_tools.py +115 -0
  371. gatewaysdk/worlds.py +639 -0
  372. gatewaysdk-0.3.2.dist-info/METADATA +236 -0
  373. gatewaysdk-0.3.2.dist-info/RECORD +376 -0
  374. gatewaysdk-0.3.2.dist-info/WHEEL +4 -0
  375. gatewaysdk-0.3.2.dist-info/entry_points.txt +5 -0
  376. 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
+ ]