pyfect-dev 0.1.0__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 (1265) hide show
  1. pyfect/__init__.py +729 -0
  2. pyfect/_all.py +242 -0
  3. pyfect/_background.py +28 -0
  4. pyfect/_binary_codecs.py +124 -0
  5. pyfect/_cached.py +165 -0
  6. pyfect/_catch.py +128 -0
  7. pyfect/_catch_reason.py +574 -0
  8. pyfect/_concurrency.py +291 -0
  9. pyfect/_context_effects.py +163 -0
  10. pyfect/_context_managers.py +76 -0
  11. pyfect/_core.py +368 -0
  12. pyfect/_digits.py +71 -0
  13. pyfect/_effect_args.py +46 -0
  14. pyfect/_effect_base.py +71 -0
  15. pyfect/_effect_concurrency.py +133 -0
  16. pyfect/_effect_eager.py +103 -0
  17. pyfect/_effect_errors.py +344 -0
  18. pyfect/_effect_folding.py +110 -0
  19. pyfect/_effect_hash.py +140 -0
  20. pyfect/_effect_logging.py +57 -0
  21. pyfect/_effect_metrics.py +102 -0
  22. pyfect/_effect_resources.py +179 -0
  23. pyfect/_effect_services.py +25 -0
  24. pyfect/_effect_timing.py +199 -0
  25. pyfect/_effect_tracing.py +99 -0
  26. pyfect/_equal.py +195 -0
  27. pyfect/_extras.py +43 -0
  28. pyfect/_fiber.py +969 -0
  29. pyfect/_fiber_all.py +134 -0
  30. pyfect/_fiber_collections.py +106 -0
  31. pyfect/_fn.py +706 -0
  32. pyfect/_hamt.py +174 -0
  33. pyfect/_interrupt_reason.py +33 -0
  34. pyfect/_isaac.py +149 -0
  35. pyfect/_iterables.py +231 -0
  36. pyfect/_js.py +263 -0
  37. pyfect/_js_date.py +87 -0
  38. pyfect/_json_text.py +286 -0
  39. pyfect/_json_value.py +9 -0
  40. pyfect/_layer_core.py +403 -0
  41. pyfect/_layer_for_life.py +33 -0
  42. pyfect/_layer_tracing.py +70 -0
  43. pyfect/_locked_ref.py +147 -0
  44. pyfect/_loop_thread.py +152 -0
  45. pyfect/_metric_ref.py +29 -0
  46. pyfect/_one_or_many.py +14 -0
  47. pyfect/_os_wait.py +118 -0
  48. pyfect/_path_posix.py +129 -0
  49. pyfect/_primitives.py +434 -0
  50. pyfect/_public.py +36 -0
  51. pyfect/_reasons.py +64 -0
  52. pyfect/_released.py +67 -0
  53. pyfect/_root.py +87 -0
  54. pyfect/_round_robin.py +73 -0
  55. pyfect/_some.py +17 -0
  56. pyfect/_stack.py +52 -0
  57. pyfect/_stack_frame.py +114 -0
  58. pyfect/_string_hash.py +35 -0
  59. pyfect/_subscriber_queue.py +47 -0
  60. pyfect/_tags.py +14 -0
  61. pyfect/_tracer_ref.py +21 -0
  62. pyfect/_tx_ops.py +77 -0
  63. pyfect/_unsubscripted.py +23 -0
  64. pyfect/_url.py +54 -0
  65. pyfect/_waiters.py +127 -0
  66. pyfect/_wake_tracking.py +98 -0
  67. pyfect/_wire_codec.py +123 -0
  68. pyfect/ai/__init__.py +73 -0
  69. pyfect/ai/_approvals.py +249 -0
  70. pyfect/ai/_decision_answers.py +185 -0
  71. pyfect/ai/_generated_client.py +298 -0
  72. pyfect/ai/_generated_support.py +84 -0
  73. pyfect/ai/_mcp/__init__.py +1 -0
  74. pyfect/ai/_mcp/batch.py +68 -0
  75. pyfect/ai/_mcp/client_transport.py +412 -0
  76. pyfect/ai/_mcp/client_wire.py +284 -0
  77. pyfect/ai/_mcp/core.py +490 -0
  78. pyfect/ai/_mcp/dispatch.py +1048 -0
  79. pyfect/ai/_mcp/http.py +462 -0
  80. pyfect/ai/_mcp/http_admission.py +269 -0
  81. pyfect/ai/_mcp/http_status.py +27 -0
  82. pyfect/ai/_mcp/input_wire.py +226 -0
  83. pyfect/ai/_mcp/log_levels.py +55 -0
  84. pyfect/ai/_mcp/param_headers.py +63 -0
  85. pyfect/ai/_mcp/projection.py +248 -0
  86. pyfect/ai/_mcp/register.py +683 -0
  87. pyfect/ai/_mcp/reverse_policy.py +155 -0
  88. pyfect/ai/_mcp/routing_header.py +24 -0
  89. pyfect/ai/_mcp/sessions.py +114 -0
  90. pyfect/ai/_mcp/stdio.py +110 -0
  91. pyfect/ai/_mcp/subscriptions.py +134 -0
  92. pyfect/ai/_mcp/tool_schema.py +35 -0
  93. pyfect/ai/_noul_answers.py +64 -0
  94. pyfect/ai/_openai_all_of.py +137 -0
  95. pyfect/ai/_openai_errors.py +233 -0
  96. pyfect/ai/_provider.py +439 -0
  97. pyfect/ai/_provider_tools.py +129 -0
  98. pyfect/ai/_stream_resolution.py +231 -0
  99. pyfect/ai/_structured_output_checks.py +138 -0
  100. pyfect/ai/_structured_output_codec.py +229 -0
  101. pyfect/ai/_structured_output_json.py +109 -0
  102. pyfect/ai/_tool_call_ids.py +80 -0
  103. pyfect/ai/ai_error.py +785 -0
  104. pyfect/ai/anthropic/__init__.py +28 -0
  105. pyfect/ai/anthropic/_errors.py +164 -0
  106. pyfect/ai/anthropic/_prompt.py +375 -0
  107. pyfect/ai/anthropic/_response.py +443 -0
  108. pyfect/ai/anthropic/_schema.py +206 -0
  109. pyfect/ai/anthropic/_stream.py +377 -0
  110. pyfect/ai/anthropic/_tools.py +248 -0
  111. pyfect/ai/anthropic/client.py +289 -0
  112. pyfect/ai/anthropic/config.py +32 -0
  113. pyfect/ai/anthropic/error.py +27 -0
  114. pyfect/ai/anthropic/language_model.py +366 -0
  115. pyfect/ai/anthropic/telemetry.py +51 -0
  116. pyfect/ai/anthropic/tool.py +570 -0
  117. pyfect/ai/anthropic_structured_output.py +138 -0
  118. pyfect/ai/chat.py +548 -0
  119. pyfect/ai/cloudflare/__init__.py +15 -0
  120. pyfect/ai/cloudflare/_errors.py +108 -0
  121. pyfect/ai/cloudflare/client.py +174 -0
  122. pyfect/ai/cloudflare/decision_model.py +117 -0
  123. pyfect/ai/cloudflare/schema.py +144 -0
  124. pyfect/ai/decision.py +290 -0
  125. pyfect/ai/decision_model.py +283 -0
  126. pyfect/ai/embedding_model.py +177 -0
  127. pyfect/ai/id_generator.py +79 -0
  128. pyfect/ai/language_model.py +1077 -0
  129. pyfect/ai/mcp_client.py +682 -0
  130. pyfect/ai/mcp_client_error.py +223 -0
  131. pyfect/ai/mcp_protocol.py +205 -0
  132. pyfect/ai/mcp_schema.py +1300 -0
  133. pyfect/ai/mcp_server.py +404 -0
  134. pyfect/ai/model.py +56 -0
  135. pyfect/ai/openai/__init__.py +49 -0
  136. pyfect/ai/openai/_errors.py +89 -0
  137. pyfect/ai/openai/_prompt.py +527 -0
  138. pyfect/ai/openai/_requests.py +34 -0
  139. pyfect/ai/openai/_response.py +569 -0
  140. pyfect/ai/openai/_schema.py +769 -0
  141. pyfect/ai/openai/_stream.py +771 -0
  142. pyfect/ai/openai/_tools.py +128 -0
  143. pyfect/ai/openai/_web_socket.py +295 -0
  144. pyfect/ai/openai/client.py +296 -0
  145. pyfect/ai/openai/client_generated.py +123 -0
  146. pyfect/ai/openai/config.py +34 -0
  147. pyfect/ai/openai/decision_model.py +177 -0
  148. pyfect/ai/openai/embedding_model.py +116 -0
  149. pyfect/ai/openai/error.py +8 -0
  150. pyfect/ai/openai/generated/__init__.py +7213 -0
  151. pyfect/ai/openai/generated/_recursive.py +18 -0
  152. pyfect/ai/openai/generated/_schemas_0.py +1015 -0
  153. pyfect/ai/openai/generated/_schemas_1.py +1016 -0
  154. pyfect/ai/openai/generated/_schemas_2.py +1017 -0
  155. pyfect/ai/openai/generated/_schemas_3.py +615 -0
  156. pyfect/ai/openai/generated/client.py +5298 -0
  157. pyfect/ai/openai/generated/types.py +8123 -0
  158. pyfect/ai/openai/language_model.py +304 -0
  159. pyfect/ai/openai/telemetry.py +19 -0
  160. pyfect/ai/openai/tool.py +391 -0
  161. pyfect/ai/openai_compat/__init__.py +25 -0
  162. pyfect/ai/openai_compat/_prompt.py +191 -0
  163. pyfect/ai/openai_compat/_request.py +153 -0
  164. pyfect/ai/openai_compat/_response.py +407 -0
  165. pyfect/ai/openai_compat/_schema.py +97 -0
  166. pyfect/ai/openai_compat/client.py +234 -0
  167. pyfect/ai/openai_compat/config.py +32 -0
  168. pyfect/ai/openai_compat/embedding_model.py +139 -0
  169. pyfect/ai/openai_compat/error.py +7 -0
  170. pyfect/ai/openai_compat/language_model.py +245 -0
  171. pyfect/ai/openai_compat/telemetry.py +51 -0
  172. pyfect/ai/openai_structured_output.py +118 -0
  173. pyfect/ai/openrouter/__init__.py +36 -0
  174. pyfect/ai/openrouter/_errors.py +186 -0
  175. pyfect/ai/openrouter/_prompt.py +272 -0
  176. pyfect/ai/openrouter/_response.py +226 -0
  177. pyfect/ai/openrouter/_stream.py +320 -0
  178. pyfect/ai/openrouter/client.py +283 -0
  179. pyfect/ai/openrouter/config.py +36 -0
  180. pyfect/ai/openrouter/decision_model.py +161 -0
  181. pyfect/ai/openrouter/error.py +23 -0
  182. pyfect/ai/openrouter/generated/__init__.py +3029 -0
  183. pyfect/ai/openrouter/generated/_recursive.py +15 -0
  184. pyfect/ai/openrouter/generated/_schemas_0.py +1015 -0
  185. pyfect/ai/openrouter/generated/_schemas_1.py +522 -0
  186. pyfect/ai/openrouter/generated/client.py +1445 -0
  187. pyfect/ai/openrouter/generated/types.py +4769 -0
  188. pyfect/ai/openrouter/language_model.py +345 -0
  189. pyfect/ai/openrouter/schema.py +165 -0
  190. pyfect/ai/prompt.py +664 -0
  191. pyfect/ai/response.py +768 -0
  192. pyfect/ai/response_id_tracker.py +82 -0
  193. pyfect/ai/telemetry.py +126 -0
  194. pyfect/ai/tokenizer.py +61 -0
  195. pyfect/ai/tool.py +536 -0
  196. pyfect/ai/toolkit.py +490 -0
  197. pyfect/ai/typesafe/__init__.py +17 -0
  198. pyfect/ai/typesafe/_errors.py +120 -0
  199. pyfect/ai/typesafe/client.py +146 -0
  200. pyfect/ai/typesafe/config.py +34 -0
  201. pyfect/ai/typesafe/decision_model.py +90 -0
  202. pyfect/ai/typesafe/schema.py +140 -0
  203. pyfect/batching/__init__.py +58 -0
  204. pyfect/batching/_api.py +71 -0
  205. pyfect/batching/_engine.py +229 -0
  206. pyfect/batching/_from_effect.py +76 -0
  207. pyfect/batching/_persisted.py +98 -0
  208. pyfect/batching/_request.py +133 -0
  209. pyfect/batching/_resolver.py +477 -0
  210. pyfect/batching/_with_cache.py +90 -0
  211. pyfect/big_decimal.py +500 -0
  212. pyfect/brand.py +114 -0
  213. pyfect/byte_size.py +354 -0
  214. pyfect/cache/__init__.py +29 -0
  215. pyfect/cache/_cache.py +426 -0
  216. pyfect/cache/_expiry.py +69 -0
  217. pyfect/cache/_scoped_cache.py +546 -0
  218. pyfect/case.py +174 -0
  219. pyfect/cause.py +462 -0
  220. pyfect/channel/__init__.py +155 -0
  221. pyfect/channel/_base.py +92 -0
  222. pyfect/channel/_catch.py +158 -0
  223. pyfect/channel/_catch_reason.py +572 -0
  224. pyfect/channel/_channel.py +158 -0
  225. pyfect/channel/_combinators.py +220 -0
  226. pyfect/channel/_concurrency_methods.py +217 -0
  227. pyfect/channel/_concurrent_map.py +118 -0
  228. pyfect/channel/_constructors.py +349 -0
  229. pyfect/channel/_context.py +232 -0
  230. pyfect/channel/_error_methods.py +336 -0
  231. pyfect/channel/_errors.py +180 -0
  232. pyfect/channel/_filters.py +225 -0
  233. pyfect/channel/_lifecycle.py +71 -0
  234. pyfect/channel/_lifecycle_methods.py +206 -0
  235. pyfect/channel/_loop.py +100 -0
  236. pyfect/channel/_merge.py +246 -0
  237. pyfect/channel/_repeat.py +154 -0
  238. pyfect/channel/_run.py +149 -0
  239. pyfect/channel/_run_methods.py +341 -0
  240. pyfect/channel/_sinks.py +88 -0
  241. pyfect/channel/_sources.py +238 -0
  242. pyfect/channel/_text.py +176 -0
  243. pyfect/channel/_transform_methods.py +415 -0
  244. pyfect/channel/_upstream.py +67 -0
  245. pyfect/channel_schema.py +131 -0
  246. pyfect/cli/__init__.py +79 -0
  247. pyfect/cli/_ansi.py +82 -0
  248. pyfect/cli/_command.py +469 -0
  249. pyfect/cli/_config.py +164 -0
  250. pyfect/cli/_environment.py +10 -0
  251. pyfect/cli/_help.py +195 -0
  252. pyfect/cli/_lexer.py +81 -0
  253. pyfect/cli/_parser.py +465 -0
  254. pyfect/cli/_settings.py +85 -0
  255. pyfect/cli/_suggest.py +33 -0
  256. pyfect/cli/_width.py +134 -0
  257. pyfect/cli/_wizard.py +317 -0
  258. pyfect/cli/argument.py +212 -0
  259. pyfect/cli/cli_config.py +40 -0
  260. pyfect/cli/cli_error.py +242 -0
  261. pyfect/cli/cli_output.py +305 -0
  262. pyfect/cli/command.py +440 -0
  263. pyfect/cli/completions/__init__.py +50 -0
  264. pyfect/cli/completions/_completer.ps1 +190 -0
  265. pyfect/cli/completions/_descriptor.py +182 -0
  266. pyfect/cli/completions/bash.py +300 -0
  267. pyfect/cli/completions/fish.py +164 -0
  268. pyfect/cli/completions/powershell.py +156 -0
  269. pyfect/cli/completions/zsh.py +189 -0
  270. pyfect/cli/flag.py +246 -0
  271. pyfect/cli/global_flag.py +99 -0
  272. pyfect/cli/help_doc.py +63 -0
  273. pyfect/cli/param.py +789 -0
  274. pyfect/cli/primitive.py +308 -0
  275. pyfect/cli/prompt/__init__.py +75 -0
  276. pyfect/cli/prompt/_auto_complete.py +205 -0
  277. pyfect/cli/prompt/_choices.py +104 -0
  278. pyfect/cli/prompt/_confirm.py +195 -0
  279. pyfect/cli/prompt/_core.py +512 -0
  280. pyfect/cli/prompt/_date.py +173 -0
  281. pyfect/cli/prompt/_date_parts.py +387 -0
  282. pyfect/cli/prompt/_file.py +372 -0
  283. pyfect/cli/prompt/_multi_select.py +204 -0
  284. pyfect/cli/prompt/_number.py +257 -0
  285. pyfect/cli/prompt/_select.py +117 -0
  286. pyfect/cli/prompt/_text.py +239 -0
  287. pyfect/cli/terminal_capabilities.py +118 -0
  288. pyfect/clock.py +150 -0
  289. pyfect/cluster/__init__.py +155 -0
  290. pyfect/cluster/_cluster_abandon.py +36 -0
  291. pyfect/cluster/_cluster_layer.py +122 -0
  292. pyfect/cluster/_entity_activation.py +16 -0
  293. pyfect/cluster/_entity_manager/__init__.py +22 -0
  294. pyfect/cluster/_entity_manager/_activation.py +540 -0
  295. pyfect/cluster/_entity_manager/_core.py +186 -0
  296. pyfect/cluster/_entity_manager/_decode.py +60 -0
  297. pyfect/cluster/_entity_manager/_manager.py +423 -0
  298. pyfect/cluster/_entity_manager/_state.py +113 -0
  299. pyfect/cluster/_entity_reaper.py +94 -0
  300. pyfect/cluster/_hash.py +13 -0
  301. pyfect/cluster/_interruptors.py +88 -0
  302. pyfect/cluster/_resource_map.py +179 -0
  303. pyfect/cluster/_resource_ref.py +266 -0
  304. pyfect/cluster/_shard_lock.py +14 -0
  305. pyfect/cluster/cluster_cron.py +230 -0
  306. pyfect/cluster/cluster_error.py +81 -0
  307. pyfect/cluster/cluster_http.py +210 -0
  308. pyfect/cluster/cluster_metrics.py +54 -0
  309. pyfect/cluster/cluster_schema.py +137 -0
  310. pyfect/cluster/cluster_socket.py +197 -0
  311. pyfect/cluster/cluster_workflow_engine/__init__.py +24 -0
  312. pyfect/cluster/cluster_workflow_engine/_engine.py +1099 -0
  313. pyfect/cluster/cluster_workflow_engine/_entities.py +298 -0
  314. pyfect/cluster/deliver_at.py +31 -0
  315. pyfect/cluster/entity/__init__.py +39 -0
  316. pyfect/cluster/entity/_entity.py +332 -0
  317. pyfect/cluster/entity/_handlers.py +168 -0
  318. pyfect/cluster/entity/_keep_alive.py +74 -0
  319. pyfect/cluster/entity/_queue.py +103 -0
  320. pyfect/cluster/entity/_request.py +34 -0
  321. pyfect/cluster/entity/_services.py +23 -0
  322. pyfect/cluster/entity/_test_client.py +237 -0
  323. pyfect/cluster/entity_address.py +25 -0
  324. pyfect/cluster/entity_id.py +20 -0
  325. pyfect/cluster/entity_proxy.py +161 -0
  326. pyfect/cluster/entity_proxy_server.py +103 -0
  327. pyfect/cluster/entity_resource.py +122 -0
  328. pyfect/cluster/entity_type.py +20 -0
  329. pyfect/cluster/envelope.py +307 -0
  330. pyfect/cluster/http_runner.py +254 -0
  331. pyfect/cluster/k8s_http_client.py +281 -0
  332. pyfect/cluster/k8s_types.py +1394 -0
  333. pyfect/cluster/machine_id.py +21 -0
  334. pyfect/cluster/message.py +219 -0
  335. pyfect/cluster/message_storage/__init__.py +67 -0
  336. pyfect/cluster/message_storage/_encoded.py +393 -0
  337. pyfect/cluster/message_storage/_memory.py +386 -0
  338. pyfect/cluster/message_storage/_noop.py +109 -0
  339. pyfect/cluster/message_storage/_service.py +388 -0
  340. pyfect/cluster/reply.py +236 -0
  341. pyfect/cluster/runner.py +32 -0
  342. pyfect/cluster/runner_address.py +23 -0
  343. pyfect/cluster/runner_health.py +93 -0
  344. pyfect/cluster/runner_server.py +286 -0
  345. pyfect/cluster/runner_storage.py +212 -0
  346. pyfect/cluster/runners/__init__.py +55 -0
  347. pyfect/cluster/runners/_local.py +460 -0
  348. pyfect/cluster/runners/_noop.py +57 -0
  349. pyfect/cluster/runners/_rpc.py +290 -0
  350. pyfect/cluster/runners/_service.py +113 -0
  351. pyfect/cluster/shard_id.py +39 -0
  352. pyfect/cluster/sharding/__init__.py +12 -0
  353. pyfect/cluster/sharding/_acquisition.py +490 -0
  354. pyfect/cluster/sharding/_clients.py +271 -0
  355. pyfect/cluster/sharding/_make.py +388 -0
  356. pyfect/cluster/sharding/_routing.py +296 -0
  357. pyfect/cluster/sharding/_runners_sync.py +198 -0
  358. pyfect/cluster/sharding/_service.py +91 -0
  359. pyfect/cluster/sharding/_singletons.py +183 -0
  360. pyfect/cluster/sharding/_state.py +296 -0
  361. pyfect/cluster/sharding/_storage_loop.py +421 -0
  362. pyfect/cluster/sharding_config.py +230 -0
  363. pyfect/cluster/sharding_registration_event.py +28 -0
  364. pyfect/cluster/single_runner.py +49 -0
  365. pyfect/cluster/singleton.py +36 -0
  366. pyfect/cluster/singleton_address.py +20 -0
  367. pyfect/cluster/snowflake.py +133 -0
  368. pyfect/cluster/socket_runner.py +67 -0
  369. pyfect/cluster/sql_message_storage.py +1001 -0
  370. pyfect/cluster/sql_runner_storage.py +841 -0
  371. pyfect/cluster/test_runner.py +23 -0
  372. pyfect/config/__init__.py +93 -0
  373. pyfect/config/_all.py +96 -0
  374. pyfect/config/_collections.py +291 -0
  375. pyfect/config/_config.py +261 -0
  376. pyfect/config/_constructors.py +106 -0
  377. pyfect/config/_error.py +27 -0
  378. pyfect/config/_leaf.py +92 -0
  379. pyfect/config/_scalars.py +193 -0
  380. pyfect/config/_schema.py +340 -0
  381. pyfect/config_provider/__init__.py +60 -0
  382. pyfect/config_provider/_dir.py +84 -0
  383. pyfect/config_provider/_dotenv.py +127 -0
  384. pyfect/config_provider/_env.py +79 -0
  385. pyfect/config_provider/_layers.py +80 -0
  386. pyfect/config_provider/_node.py +90 -0
  387. pyfect/config_provider/_provider.py +118 -0
  388. pyfect/config_provider/_unknown.py +62 -0
  389. pyfect/console/__init__.py +63 -0
  390. pyfect/console/_effects.py +134 -0
  391. pyfect/console/_live.py +156 -0
  392. pyfect/console/_service.py +49 -0
  393. pyfect/console/_table.py +90 -0
  394. pyfect/context.py +414 -0
  395. pyfect/cron/__init__.py +30 -0
  396. pyfect/cron/_model.py +183 -0
  397. pyfect/cron/_search.py +268 -0
  398. pyfect/cron/_text.py +202 -0
  399. pyfect/cron/_wall.py +42 -0
  400. pyfect/crypto.py +257 -0
  401. pyfect/date_time/__init__.py +77 -0
  402. pyfect/date_time/_calendar.py +209 -0
  403. pyfect/date_time/_current.py +118 -0
  404. pyfect/date_time/_epoch.py +22 -0
  405. pyfect/date_time/_iso.py +80 -0
  406. pyfect/date_time/_zones.py +135 -0
  407. pyfect/date_time/intl.py +144 -0
  408. pyfect/deferred.py +145 -0
  409. pyfect/devtools/__init__.py +24 -0
  410. pyfect/devtools/client.py +323 -0
  411. pyfect/devtools/layers.py +32 -0
  412. pyfect/devtools/schema.py +301 -0
  413. pyfect/devtools/server.py +72 -0
  414. pyfect/duration.py +295 -0
  415. pyfect/effect.py +565 -0
  416. pyfect/effectable.py +37 -0
  417. pyfect/encoding/__init__.py +62 -0
  418. pyfect/encoding/_archive.py +197 -0
  419. pyfect/encoding/_json_array_scanner.py +477 -0
  420. pyfect/encoding/base64.py +23 -0
  421. pyfect/encoding/base64_url.py +19 -0
  422. pyfect/encoding/encoding_error.py +17 -0
  423. pyfect/encoding/hex.py +28 -0
  424. pyfect/encoding/ini.py +188 -0
  425. pyfect/encoding/json_array.py +246 -0
  426. pyfect/encoding/ndjson.py +461 -0
  427. pyfect/encoding/schema_binary/__init__.py +257 -0
  428. pyfect/encoding/schema_binary/_channels.py +173 -0
  429. pyfect/encoding/schema_binary/_compile.py +584 -0
  430. pyfect/encoding/schema_binary/_decode.py +848 -0
  431. pyfect/encoding/schema_binary/_dictionary.py +90 -0
  432. pyfect/encoding/schema_binary/_encode.py +838 -0
  433. pyfect/encoding/schema_binary/_fingerprint.py +168 -0
  434. pyfect/encoding/schema_binary/_layout.py +350 -0
  435. pyfect/encoding/schema_binary/_literals.py +47 -0
  436. pyfect/encoding/schema_binary/_parser.py +163 -0
  437. pyfect/encoding/schema_binary/_reader.py +148 -0
  438. pyfect/encoding/schema_binary/_target.py +310 -0
  439. pyfect/encoding/schema_binary/_values.py +133 -0
  440. pyfect/encoding/schema_binary/_wire.py +253 -0
  441. pyfect/encoding/sse.py +476 -0
  442. pyfect/encoding/tar/__init__.py +396 -0
  443. pyfect/encoding/tar/_decoder.py +220 -0
  444. pyfect/encoding/tar/_encoder.py +83 -0
  445. pyfect/encoding/tar/_header.py +494 -0
  446. pyfect/encoding/tar/_model.py +82 -0
  447. pyfect/encoding/toml.py +375 -0
  448. pyfect/encoding/xml/__init__.py +222 -0
  449. pyfect/encoding/xml/_chars.py +119 -0
  450. pyfect/encoding/xml/_collector.py +106 -0
  451. pyfect/encoding/xml/_inputs.py +126 -0
  452. pyfect/encoding/xml/_model.py +114 -0
  453. pyfect/encoding/xml/_parser.py +694 -0
  454. pyfect/encoding/xml/_scanner.py +180 -0
  455. pyfect/encoding/xml/_writer.py +238 -0
  456. pyfect/encoding/yaml.py +739 -0
  457. pyfect/encoding/zip/__init__.py +392 -0
  458. pyfect/encoding/zip/_decoder.py +378 -0
  459. pyfect/encoding/zip/_format.py +122 -0
  460. pyfect/encoding/zip/_inflate.py +65 -0
  461. pyfect/encoding/zip/_model.py +86 -0
  462. pyfect/encoding/zip/_writer.py +311 -0
  463. pyfect/error_reporter.py +240 -0
  464. pyfect/errors.py +321 -0
  465. pyfect/eventlog/__init__.py +77 -0
  466. pyfect/eventlog/_conflicts.py +94 -0
  467. pyfect/eventlog/_crypto.py +64 -0
  468. pyfect/eventlog/_identity.py +26 -0
  469. pyfect/eventlog/_identity_derivation.py +79 -0
  470. pyfect/eventlog/_transferable_bytes.py +55 -0
  471. pyfect/eventlog/_uuid.py +33 -0
  472. pyfect/eventlog/event.py +115 -0
  473. pyfect/eventlog/event_group.py +55 -0
  474. pyfect/eventlog/event_journal.py +630 -0
  475. pyfect/eventlog/event_log.py +1039 -0
  476. pyfect/eventlog/event_log_encryption.py +219 -0
  477. pyfect/eventlog/event_log_message.py +357 -0
  478. pyfect/eventlog/event_log_remote.py +465 -0
  479. pyfect/eventlog/event_log_server.py +442 -0
  480. pyfect/eventlog/event_log_server_encrypted.py +322 -0
  481. pyfect/eventlog/event_log_server_unencrypted.py +814 -0
  482. pyfect/eventlog/event_log_session_auth.py +250 -0
  483. pyfect/eventlog/event_row.py +423 -0
  484. pyfect/eventlog/sql_event_journal.py +692 -0
  485. pyfect/eventlog/sql_event_log_server_encrypted.py +440 -0
  486. pyfect/eventlog/sql_event_log_server_unencrypted.py +568 -0
  487. pyfect/execution_plan/__init__.py +42 -0
  488. pyfect/execution_plan/_events.py +128 -0
  489. pyfect/execution_plan/_model.py +108 -0
  490. pyfect/execution_plan/_run.py +176 -0
  491. pyfect/exit.py +205 -0
  492. pyfect/fiber_handle.py +305 -0
  493. pyfect/fiber_map.py +350 -0
  494. pyfect/fiber_set.py +232 -0
  495. pyfect/file_system/__init__.py +69 -0
  496. pyfect/file_system/_copy.py +116 -0
  497. pyfect/file_system/_errors.py +32 -0
  498. pyfect/file_system/_file.py +177 -0
  499. pyfect/file_system/_live.py +351 -0
  500. pyfect/file_system/_model.py +72 -0
  501. pyfect/file_system/_noop.py +189 -0
  502. pyfect/file_system/_service.py +336 -0
  503. pyfect/file_system/_stat.py +69 -0
  504. pyfect/file_system/_watch.py +107 -0
  505. pyfect/filters.py +245 -0
  506. pyfect/graph/__init__.py +101 -0
  507. pyfect/graph/_all_pairs.py +150 -0
  508. pyfect/graph/_combine.py +259 -0
  509. pyfect/graph/_compact.py +143 -0
  510. pyfect/graph/_components.py +200 -0
  511. pyfect/graph/_cycles.py +144 -0
  512. pyfect/graph/_enumerate.py +168 -0
  513. pyfect/graph/_errors.py +51 -0
  514. pyfect/graph/_flow.py +164 -0
  515. pyfect/graph/_forest.py +109 -0
  516. pyfect/graph/_graph.py +269 -0
  517. pyfect/graph/_lowlink.py +98 -0
  518. pyfect/graph/_matching.py +101 -0
  519. pyfect/graph/_mutate.py +211 -0
  520. pyfect/graph/_query.py +138 -0
  521. pyfect/graph/_records.py +119 -0
  522. pyfect/graph/_render.py +109 -0
  523. pyfect/graph/_shortest.py +274 -0
  524. pyfect/graph/_snapshot.py +50 -0
  525. pyfect/graph/_store.py +136 -0
  526. pyfect/graph/_traverse.py +289 -0
  527. pyfect/graph/_view.py +384 -0
  528. pyfect/graph/_walker.py +70 -0
  529. pyfect/graphql/__init__.py +87 -0
  530. pyfect/graphql/_codec.py +117 -0
  531. pyfect/graphql/_http.py +309 -0
  532. pyfect/graphql/_schemas.py +39 -0
  533. pyfect/graphql/_slices.py +110 -0
  534. pyfect/graphql/_template.py +111 -0
  535. pyfect/graphql/_types.py +115 -0
  536. pyfect/graphql/_ws.py +395 -0
  537. pyfect/graphql/_ws_messages.py +120 -0
  538. pyfect/graphql/client.py +428 -0
  539. pyfect/graphql/client_error.py +285 -0
  540. pyfect/graphql/group.py +142 -0
  541. pyfect/graphql/middleware.py +98 -0
  542. pyfect/graphql/operation.py +409 -0
  543. pyfect/graphql/protocol.py +119 -0
  544. pyfect/graphql/resolver.py +574 -0
  545. pyfect/graphql/template.py +61 -0
  546. pyfect/hash_map.py +317 -0
  547. pyfect/hash_ring.py +206 -0
  548. pyfect/hash_set.py +163 -0
  549. pyfect/http/__init__.py +125 -0
  550. pyfect/http/_asgi_listener.py +272 -0
  551. pyfect/http/_authority.py +63 -0
  552. pyfect/http/_brotli.py +27 -0
  553. pyfect/http/_compression.py +52 -0
  554. pyfect/http/_field.py +18 -0
  555. pyfect/http/_foreign_path.py +27 -0
  556. pyfect/http/_form_data.py +34 -0
  557. pyfect/http/_httpx_options.py +120 -0
  558. pyfect/http/_in_memory.py +76 -0
  559. pyfect/http/_request_state.py +59 -0
  560. pyfect/http/_vary.py +14 -0
  561. pyfect/http/abort.py +65 -0
  562. pyfect/http/asgi/__init__.py +59 -0
  563. pyfect/http/asgi/_app.py +155 -0
  564. pyfect/http/asgi/_http.py +201 -0
  565. pyfect/http/asgi/_mount.py +263 -0
  566. pyfect/http/asgi/_scope.py +86 -0
  567. pyfect/http/asgi/_serve.py +72 -0
  568. pyfect/http/asgi/_types.py +13 -0
  569. pyfect/http/asgi/_web_socket.py +325 -0
  570. pyfect/http/body.py +437 -0
  571. pyfect/http/client/__init__.py +111 -0
  572. pyfect/http/client/_circuit_breaker.py +113 -0
  573. pyfect/http/client/_cookie_jar.py +120 -0
  574. pyfect/http/client/_make.py +252 -0
  575. pyfect/http/client/_rate_limit.py +423 -0
  576. pyfect/http/client/_redirects.py +93 -0
  577. pyfect/http/client/_retry.py +115 -0
  578. pyfect/http/client/_service.py +497 -0
  579. pyfect/http/cookies.py +444 -0
  580. pyfect/http/error.py +188 -0
  581. pyfect/http/etag.py +96 -0
  582. pyfect/http/file_like.py +49 -0
  583. pyfect/http/find_my_way/__init__.py +10 -0
  584. pyfect/http/find_my_way/_nodes.py +186 -0
  585. pyfect/http/find_my_way/_paths.py +43 -0
  586. pyfect/http/find_my_way/_query_string.py +67 -0
  587. pyfect/http/find_my_way/_router.py +401 -0
  588. pyfect/http/headers.py +343 -0
  589. pyfect/http/http_effect.py +198 -0
  590. pyfect/http/httpx_client.py +291 -0
  591. pyfect/http/hypercorn_server.py +122 -0
  592. pyfect/http/incoming_message.py +140 -0
  593. pyfect/http/live_server/__init__.py +191 -0
  594. pyfect/http/live_server/_body.py +164 -0
  595. pyfect/http/live_server/_exchange.py +321 -0
  596. pyfect/http/live_server/_head.py +194 -0
  597. pyfect/http/live_server/_inbound.py +156 -0
  598. pyfect/http/live_server/_listener.py +211 -0
  599. pyfect/http/live_server/_serve.py +225 -0
  600. pyfect/http/live_server/_timers.py +20 -0
  601. pyfect/http/live_server/_upgrade.py +108 -0
  602. pyfect/http/live_server/_websocket.py +286 -0
  603. pyfect/http/live_server/_ws_frames.py +242 -0
  604. pyfect/http/method.py +30 -0
  605. pyfect/http/middleware/__init__.py +61 -0
  606. pyfect/http/middleware/_compression.py +156 -0
  607. pyfect/http/middleware/_cors.py +129 -0
  608. pyfect/http/middleware/_decompression.py +274 -0
  609. pyfect/http/middleware/_forwarded.py +113 -0
  610. pyfect/http/middleware/_logger.py +73 -0
  611. pyfect/http/middleware/_search_params.py +34 -0
  612. pyfect/http/middleware/_tracer.py +153 -0
  613. pyfect/http/middleware/_types.py +16 -0
  614. pyfect/http/mime/__init__.py +69 -0
  615. pyfect/http/mime/_types.py +361 -0
  616. pyfect/http/multipart.py +613 -0
  617. pyfect/http/multipart_parser/__init__.py +348 -0
  618. pyfect/http/multipart_parser/_content_type.py +109 -0
  619. pyfect/http/multipart_parser/_headers.py +206 -0
  620. pyfect/http/multipart_parser/_search.py +85 -0
  621. pyfect/http/origin.py +146 -0
  622. pyfect/http/platform.py +593 -0
  623. pyfect/http/prometheus.py +34 -0
  624. pyfect/http/request.py +384 -0
  625. pyfect/http/response.py +474 -0
  626. pyfect/http/router/__init__.py +82 -0
  627. pyfect/http/router/_api.py +462 -0
  628. pyfect/http/router/_in_memory.py +76 -0
  629. pyfect/http/router/_markers.py +47 -0
  630. pyfect/http/router/_middleware.py +181 -0
  631. pyfect/http/router/_middleware_stack.py +51 -0
  632. pyfect/http/router/_provided.py +31 -0
  633. pyfect/http/router/_route.py +114 -0
  634. pyfect/http/router/_router.py +308 -0
  635. pyfect/http/schema.py +276 -0
  636. pyfect/http/server.py +199 -0
  637. pyfect/http/server_error.py +261 -0
  638. pyfect/http/server_request.py +729 -0
  639. pyfect/http/server_respondable.py +77 -0
  640. pyfect/http/server_response.py +569 -0
  641. pyfect/http/static_server/__init__.py +257 -0
  642. pyfect/http/static_server/_conditional.py +72 -0
  643. pyfect/http/static_server/_mime.py +55 -0
  644. pyfect/http/static_server/_range.py +53 -0
  645. pyfect/http/status.py +51 -0
  646. pyfect/http/template.py +161 -0
  647. pyfect/http/trace_context.py +91 -0
  648. pyfect/http/url.py +442 -0
  649. pyfect/http/url_params.py +304 -0
  650. pyfect/http/uvicorn_server.py +119 -0
  651. pyfect/http/web.py +171 -0
  652. pyfect/http/web_handler.py +463 -0
  653. pyfect/http/wire.py +209 -0
  654. pyfect/http_api/__init__.py +83 -0
  655. pyfect/http_api/_assets/scalar.js +8 -0
  656. pyfect/http_api/_assets/swagger-ui.css +3 -0
  657. pyfect/http_api/_assets/swagger-ui.js +4 -0
  658. pyfect/http_api/_client_method.py +220 -0
  659. pyfect/http_api/_client_payload.py +144 -0
  660. pyfect/http_api/_client_response.py +377 -0
  661. pyfect/http_api/_handlers.py +282 -0
  662. pyfect/http_api/_html.py +38 -0
  663. pyfect/http_api/_media_type.py +6 -0
  664. pyfect/http_api/_openapi_responses.py +164 -0
  665. pyfect/http_api/_openapi_schemas.py +163 -0
  666. pyfect/http_api/_path.py +102 -0
  667. pyfect/http_api/_payload.py +260 -0
  668. pyfect/http_api/_response.py +363 -0
  669. pyfect/http_api/_security.py +174 -0
  670. pyfect/http_api/api.py +265 -0
  671. pyfect/http_api/builder.py +447 -0
  672. pyfect/http_api/client.py +474 -0
  673. pyfect/http_api/endpoint.py +969 -0
  674. pyfect/http_api/error.py +280 -0
  675. pyfect/http_api/group.py +215 -0
  676. pyfect/http_api/middleware.py +232 -0
  677. pyfect/http_api/openapi.py +396 -0
  678. pyfect/http_api/scalar.py +143 -0
  679. pyfect/http_api/schema.py +713 -0
  680. pyfect/http_api/security.py +91 -0
  681. pyfect/http_api/swagger.py +69 -0
  682. pyfect/http_api/test.py +105 -0
  683. pyfect/json_patch.py +255 -0
  684. pyfect/json_pointer.py +100 -0
  685. pyfect/json_schema/__init__.py +66 -0
  686. pyfect/json_schema/_api.py +96 -0
  687. pyfect/json_schema/_converter.py +258 -0
  688. pyfect/json_schema/_draft04.py +198 -0
  689. pyfect/json_schema/_draft07.py +203 -0
  690. pyfect/json_schema/_keywords.py +202 -0
  691. pyfect/json_schema/_model.py +201 -0
  692. pyfect/json_schema/_openapi.py +208 -0
  693. pyfect/latch.py +127 -0
  694. pyfect/layer.py +479 -0
  695. pyfect/layer_map.py +279 -0
  696. pyfect/layer_ref.py +208 -0
  697. pyfect/logger/__init__.py +91 -0
  698. pyfect/logger/_batched.py +49 -0
  699. pyfect/logger/_console.py +116 -0
  700. pyfect/logger/_file.py +31 -0
  701. pyfect/logger/_formats.py +104 -0
  702. pyfect/logger/_layer.py +50 -0
  703. pyfect/logger/_level.py +53 -0
  704. pyfect/logger/_log.py +142 -0
  705. pyfect/logger/_model.py +163 -0
  706. pyfect/logger/_refs.py +78 -0
  707. pyfect/logger/_stdlib.py +54 -0
  708. pyfect/logger/_text.py +263 -0
  709. pyfect/logger/_tracer.py +35 -0
  710. pyfect/managed_runtime.py +393 -0
  711. pyfect/metric/__init__.py +119 -0
  712. pyfect/metric/_boundaries.py +27 -0
  713. pyfect/metric/_kinds.py +417 -0
  714. pyfect/metric/_metric.py +302 -0
  715. pyfect/metric/_prometheus.py +150 -0
  716. pyfect/metric/_refs.py +78 -0
  717. pyfect/metric/_runtime.py +75 -0
  718. pyfect/metric/_snapshot.py +97 -0
  719. pyfect/metric/_state.py +74 -0
  720. pyfect/msgspec.py +98 -0
  721. pyfect/mutable_hash_map.py +140 -0
  722. pyfect/mutable_hash_set.py +95 -0
  723. pyfect/net/__init__.py +18 -0
  724. pyfect/net/_errors.py +29 -0
  725. pyfect/net/address_resolver.py +179 -0
  726. pyfect/net/dns/__init__.py +92 -0
  727. pyfect/net/dns/_error.py +43 -0
  728. pyfect/net/dns/_records.py +335 -0
  729. pyfect/net/dns/_service.py +287 -0
  730. pyfect/net/dns/_system.py +89 -0
  731. pyfect/net/host.py +311 -0
  732. pyfect/net/ip_interface.py +202 -0
  733. pyfect/net/ip_network.py +250 -0
  734. pyfect/net/net_address/__init__.py +245 -0
  735. pyfect/net/net_address/_inet.py +412 -0
  736. pyfect/net/net_address/_ip.py +348 -0
  737. pyfect/net/net_address/_mac.py +119 -0
  738. pyfect/optic/__init__.py +50 -0
  739. pyfect/optic/_builtins.py +90 -0
  740. pyfect/optic/_data.py +97 -0
  741. pyfect/optic/_optic.py +422 -0
  742. pyfect/optic/_steps.py +151 -0
  743. pyfect/option.py +362 -0
  744. pyfect/otel/__init__.py +22 -0
  745. pyfect/otel/_attributes.py +67 -0
  746. pyfect/otel/_producer.py +322 -0
  747. pyfect/otel/_shutdown.py +26 -0
  748. pyfect/otel/_span.py +292 -0
  749. pyfect/otel/_wrapper.py +92 -0
  750. pyfect/otel/logger.py +154 -0
  751. pyfect/otel/metrics.py +126 -0
  752. pyfect/otel/resource.py +111 -0
  753. pyfect/otel/sdk.py +195 -0
  754. pyfect/otel/tracer.py +157 -0
  755. pyfect/partitioned_semaphore.py +211 -0
  756. pyfect/path.py +304 -0
  757. pyfect/persistence/__init__.py +77 -0
  758. pyfect/persistence/_columns.py +32 -0
  759. pyfect/persistence/_error.py +14 -0
  760. pyfect/persistence/_seconds.py +68 -0
  761. pyfect/persistence/_sql_keys.py +47 -0
  762. pyfect/persistence/circuit_breaker/__init__.py +120 -0
  763. pyfect/persistence/circuit_breaker/_breaker.py +317 -0
  764. pyfect/persistence/circuit_breaker/_errors.py +67 -0
  765. pyfect/persistence/circuit_breaker/_memory.py +243 -0
  766. pyfect/persistence/circuit_breaker/_redis.py +509 -0
  767. pyfect/persistence/circuit_breaker/_store.py +60 -0
  768. pyfect/persistence/circuit_breaker/_types.py +191 -0
  769. pyfect/persistence/key_value_store/__init__.py +51 -0
  770. pyfect/persistence/key_value_store/_layers.py +216 -0
  771. pyfect/persistence/key_value_store/_schema_store.py +91 -0
  772. pyfect/persistence/key_value_store/_sql.py +197 -0
  773. pyfect/persistence/key_value_store/_store.py +212 -0
  774. pyfect/persistence/persistable.py +164 -0
  775. pyfect/persistence/persisted_cache.py +159 -0
  776. pyfect/persistence/persisted_queue/__init__.py +75 -0
  777. pyfect/persistence/persisted_queue/_error.py +42 -0
  778. pyfect/persistence/persisted_queue/_mailbox.py +101 -0
  779. pyfect/persistence/persisted_queue/_memory.py +182 -0
  780. pyfect/persistence/persisted_queue/_queue.py +272 -0
  781. pyfect/persistence/persisted_queue/_redis.py +455 -0
  782. pyfect/persistence/persisted_queue/_redis_scripts.py +395 -0
  783. pyfect/persistence/persisted_queue/_sql.py +334 -0
  784. pyfect/persistence/persisted_queue/_sql_migrations.py +319 -0
  785. pyfect/persistence/persisted_queue/_sql_statements.py +441 -0
  786. pyfect/persistence/persisted_queue/_store.py +147 -0
  787. pyfect/persistence/persistence/__init__.py +84 -0
  788. pyfect/persistence/persistence/_kvs.py +135 -0
  789. pyfect/persistence/persistence/_memory.py +62 -0
  790. pyfect/persistence/persistence/_redis.py +162 -0
  791. pyfect/persistence/persistence/_service.py +276 -0
  792. pyfect/persistence/persistence/_sql.py +499 -0
  793. pyfect/persistence/rate_limiter/__init__.py +82 -0
  794. pyfect/persistence/rate_limiter/_errors.py +59 -0
  795. pyfect/persistence/rate_limiter/_limiter.py +325 -0
  796. pyfect/persistence/rate_limiter/_memory.py +217 -0
  797. pyfect/persistence/rate_limiter/_redis.py +485 -0
  798. pyfect/persistence/rate_limiter/_store.py +89 -0
  799. pyfect/persistence/redis/__init__.py +52 -0
  800. pyfect/persistence/redis/_error.py +12 -0
  801. pyfect/persistence/redis/_make.py +234 -0
  802. pyfect/persistence/redis/_script.py +78 -0
  803. pyfect/persistence/redis/_service.py +104 -0
  804. pyfect/persistence/redis_py.py +303 -0
  805. pyfect/persistence/user_directories.py +195 -0
  806. pyfect/platform.py +26 -0
  807. pyfect/platform_error.py +256 -0
  808. pyfect/pool/__init__.py +36 -0
  809. pyfect/pool/_pool.py +707 -0
  810. pyfect/pool/_state.py +161 -0
  811. pyfect/pool/_ttl.py +105 -0
  812. pyfect/pool/_waiters.py +116 -0
  813. pyfect/primary_key.py +35 -0
  814. pyfect/process/__init__.py +82 -0
  815. pyfect/process/_child.py +213 -0
  816. pyfect/process/_command.py +358 -0
  817. pyfect/process/_handle.py +77 -0
  818. pyfect/process/_live.py +455 -0
  819. pyfect/process/_os.py +78 -0
  820. pyfect/process/_pipes.py +291 -0
  821. pyfect/process/_spawner.py +75 -0
  822. pyfect/pubsub/__init__.py +33 -0
  823. pyfect/pubsub/_atomic.py +181 -0
  824. pyfect/pubsub/_hub.py +214 -0
  825. pyfect/pubsub/_pubsub.py +214 -0
  826. pyfect/pubsub/_replay.py +107 -0
  827. pyfect/pubsub/_subscription.py +226 -0
  828. pyfect/pull.py +132 -0
  829. pyfect/py.typed +0 -0
  830. pyfect/pydantic.py +188 -0
  831. pyfect/queue/__init__.py +33 -0
  832. pyfect/queue/_queue.py +525 -0
  833. pyfect/queue/_state.py +111 -0
  834. pyfect/queue/_views.py +55 -0
  835. pyfect/random.py +146 -0
  836. pyfect/rc_map.py +363 -0
  837. pyfect/rc_ref.py +271 -0
  838. pyfect/reactivity/__init__.py +53 -0
  839. pyfect/reactivity/_async_result_schema.py +227 -0
  840. pyfect/reactivity/_hydration.py +18 -0
  841. pyfect/reactivity/_live.py +21 -0
  842. pyfect/reactivity/_reactivity.py +256 -0
  843. pyfect/reactivity/_result_effect.py +17 -0
  844. pyfect/reactivity/_time.py +11 -0
  845. pyfect/reactivity/async_result.py +679 -0
  846. pyfect/reactivity/atom/__init__.py +143 -0
  847. pyfect/reactivity/atom/_combinators.py +223 -0
  848. pyfect/reactivity/atom/_constructors.py +80 -0
  849. pyfect/reactivity/atom/_conversions.py +79 -0
  850. pyfect/reactivity/atom/_effect.py +252 -0
  851. pyfect/reactivity/atom/_fn.py +341 -0
  852. pyfect/reactivity/atom/_kvs.py +101 -0
  853. pyfect/reactivity/atom/_optimistic.py +162 -0
  854. pyfect/reactivity/atom/_runtime.py +323 -0
  855. pyfect/reactivity/atom/_serializable.py +65 -0
  856. pyfect/reactivity/atom/_subscription_ref.py +102 -0
  857. pyfect/reactivity/atom/_swr.py +69 -0
  858. pyfect/reactivity/atom/_types.py +435 -0
  859. pyfect/reactivity/atom_http_api.py +667 -0
  860. pyfect/reactivity/atom_ref.py +260 -0
  861. pyfect/reactivity/atom_registry/__init__.py +219 -0
  862. pyfect/reactivity/atom_registry/_batch.py +96 -0
  863. pyfect/reactivity/atom_registry/_dispatcher.py +60 -0
  864. pyfect/reactivity/atom_registry/_lifetime.py +202 -0
  865. pyfect/reactivity/atom_registry/_node.py +423 -0
  866. pyfect/reactivity/atom_registry/_registry.py +380 -0
  867. pyfect/reactivity/atom_rpc.py +283 -0
  868. pyfect/reactivity/hydration.py +200 -0
  869. pyfect/redacted.py +83 -0
  870. pyfect/ref.py +129 -0
  871. pyfect/resource.py +98 -0
  872. pyfect/result.py +306 -0
  873. pyfect/rpc/__init__.py +64 -0
  874. pyfect/rpc/_encoded_schema.py +86 -0
  875. pyfect/rpc/_json_rpc.py +246 -0
  876. pyfect/rpc/_methods.py +177 -0
  877. pyfect/rpc/_rpc.py +498 -0
  878. pyfect/rpc/client/__init__.py +100 -0
  879. pyfect/rpc/client/_core.py +449 -0
  880. pyfect/rpc/client/_http.py +257 -0
  881. pyfect/rpc/client/_protocol.py +171 -0
  882. pyfect/rpc/client/_serialized.py +321 -0
  883. pyfect/rpc/client/_transports.py +328 -0
  884. pyfect/rpc/client/_worker.py +283 -0
  885. pyfect/rpc/client_error.py +35 -0
  886. pyfect/rpc/group.py +296 -0
  887. pyfect/rpc/message.py +299 -0
  888. pyfect/rpc/middleware.py +182 -0
  889. pyfect/rpc/schema.py +89 -0
  890. pyfect/rpc/serialization.py +366 -0
  891. pyfect/rpc/server/__init__.py +181 -0
  892. pyfect/rpc/server/_core.py +549 -0
  893. pyfect/rpc/server/_protocol.py +123 -0
  894. pyfect/rpc/server/_serialized.py +363 -0
  895. pyfect/rpc/server/_stdio.py +101 -0
  896. pyfect/rpc/server/_transports.py +427 -0
  897. pyfect/rpc/server/_worker.py +108 -0
  898. pyfect/rpc/test.py +74 -0
  899. pyfect/rpc/worker.py +92 -0
  900. pyfect/run.py +393 -0
  901. pyfect/schedule/__init__.py +91 -0
  902. pyfect/schedule/_combinators.py +168 -0
  903. pyfect/schedule/_constructors.py +223 -0
  904. pyfect/schedule/_cron.py +49 -0
  905. pyfect/schedule/_drive.py +191 -0
  906. pyfect/schedule/_model.py +405 -0
  907. pyfect/scheduler.py +575 -0
  908. pyfect/schema/__init__.py +866 -0
  909. pyfect/schema/_adapters.py +39 -0
  910. pyfect/schema/_arbitrary_constraint.py +242 -0
  911. pyfect/schema/_arbitrary_graph.py +325 -0
  912. pyfect/schema/_arbitrary_links.py +179 -0
  913. pyfect/schema/_arbitrary_number.py +112 -0
  914. pyfect/schema/_arrays.py +287 -0
  915. pyfect/schema/_assignable.py +118 -0
  916. pyfect/schema/_boolean_literals.py +31 -0
  917. pyfect/schema/_checks_number.py +216 -0
  918. pyfect/schema/_checks_order.py +140 -0
  919. pyfect/schema/_checks_size.py +277 -0
  920. pyfect/schema/_checks_string.py +241 -0
  921. pyfect/schema/_class.py +569 -0
  922. pyfect/schema/_codec_derivation.py +227 -0
  923. pyfect/schema/_codec_links.py +221 -0
  924. pyfect/schema/_codecs.py +316 -0
  925. pyfect/schema/_collections.py +172 -0
  926. pyfect/schema/_constructor_default.py +89 -0
  927. pyfect/schema/_data.py +440 -0
  928. pyfect/schema/_data_codecs.py +145 -0
  929. pyfect/schema/_decimal_regexp.py +243 -0
  930. pyfect/schema/_declare.py +68 -0
  931. pyfect/schema/_derive.py +292 -0
  932. pyfect/schema/_duration_codecs.py +27 -0
  933. pyfect/schema/_ecma_pattern.py +416 -0
  934. pyfect/schema/_error.py +26 -0
  935. pyfect/schema/_error_instance.py +222 -0
  936. pyfect/schema/_graph.py +271 -0
  937. pyfect/schema/_instances.py +240 -0
  938. pyfect/schema/_jsonschema/__init__.py +90 -0
  939. pyfect/schema/_jsonschema/_annotations.py +114 -0
  940. pyfect/schema/_jsonschema/_checks.py +253 -0
  941. pyfect/schema/_jsonschema/_compile.py +346 -0
  942. pyfect/schema/_jsonschema/_exact.py +19 -0
  943. pyfect/schema/_jsonschema/_merge.py +211 -0
  944. pyfect/schema/_jsonschema/_objects.py +231 -0
  945. pyfect/schema/_jsonschema/_references.py +221 -0
  946. pyfect/schema/_jsonschema/_template.py +69 -0
  947. pyfect/schema/_literals.py +147 -0
  948. pyfect/schema/_middleware.py +209 -0
  949. pyfect/schema/_net.py +522 -0
  950. pyfect/schema/_options.py +49 -0
  951. pyfect/schema/_parse.py +351 -0
  952. pyfect/schema/_parsed_text.py +37 -0
  953. pyfect/schema/_primitives.py +48 -0
  954. pyfect/schema/_record.py +41 -0
  955. pyfect/schema/_refine.py +81 -0
  956. pyfect/schema/_schema.py +158 -0
  957. pyfect/schema/_semver.py +144 -0
  958. pyfect/schema/_strategy/__init__.py +258 -0
  959. pyfect/schema/_strategy/_big_decimal.py +203 -0
  960. pyfect/schema/_strategy/_composites.py +119 -0
  961. pyfect/schema/_strategy/_leaves.py +94 -0
  962. pyfect/schema/_strategy/_net.py +133 -0
  963. pyfect/schema/_struct.py +87 -0
  964. pyfect/schema/_struct_keys.py +128 -0
  965. pyfect/schema/_tagged.py +254 -0
  966. pyfect/schema/_template_literal.py +172 -0
  967. pyfect/schema/_to_codec_array_from_single.py +115 -0
  968. pyfect/schema/_to_codec_go_json.py +174 -0
  969. pyfect/schema/_to_codec_json.py +73 -0
  970. pyfect/schema/_to_codec_string_tree.py +115 -0
  971. pyfect/schema/_to_differ_json_patch.py +44 -0
  972. pyfect/schema/_to_encoder_xml.py +119 -0
  973. pyfect/schema/_to_equivalence.py +186 -0
  974. pyfect/schema/_to_formatter.py +180 -0
  975. pyfect/schema/_to_iso.py +73 -0
  976. pyfect/schema/_to_strategy.py +31 -0
  977. pyfect/schema/_transform.py +210 -0
  978. pyfect/schema/_tree.py +27 -0
  979. pyfect/schema/_type_walk.py +53 -0
  980. pyfect/schema/_undefined.py +58 -0
  981. pyfect/schema/_value_codecs.py +84 -0
  982. pyfect/schema/_values.py +123 -0
  983. pyfect/schema/_void.py +86 -0
  984. pyfect/schema/ast/__init__.py +227 -0
  985. pyfect/schema/ast/_annotations.py +43 -0
  986. pyfect/schema/ast/_arrays.py +210 -0
  987. pyfect/schema/ast/_base.py +295 -0
  988. pyfect/schema/ast/_candidates.py +309 -0
  989. pyfect/schema/ast/_check.py +181 -0
  990. pyfect/schema/ast/_class.py +159 -0
  991. pyfect/schema/ast/_constructors.py +65 -0
  992. pyfect/schema/ast/_declaration.py +97 -0
  993. pyfect/schema/ast/_encoding.py +111 -0
  994. pyfect/schema/ast/_json.py +129 -0
  995. pyfect/schema/ast/_keywords.py +125 -0
  996. pyfect/schema/ast/_literal.py +157 -0
  997. pyfect/schema/ast/_objects.py +356 -0
  998. pyfect/schema/ast/_record.py +36 -0
  999. pyfect/schema/ast/_result.py +116 -0
  1000. pyfect/schema/ast/_steps.py +209 -0
  1001. pyfect/schema/ast/_suspend.py +65 -0
  1002. pyfect/schema/ast/_template_literal.py +256 -0
  1003. pyfect/schema/ast/_union.py +172 -0
  1004. pyfect/schema/getter/__init__.py +136 -0
  1005. pyfect/schema/getter/_binary.py +104 -0
  1006. pyfect/schema/getter/_coerce.py +196 -0
  1007. pyfect/schema/getter/_getter.py +243 -0
  1008. pyfect/schema/getter/_make.py +151 -0
  1009. pyfect/schema/getter/_text.py +141 -0
  1010. pyfect/schema/getter/_tree_record.py +170 -0
  1011. pyfect/schema/issue.py +420 -0
  1012. pyfect/schema/model/__init__.py +120 -0
  1013. pyfect/schema/model/_class.py +245 -0
  1014. pyfect/schema/model/_dates.py +103 -0
  1015. pyfect/schema/model/_fields.py +77 -0
  1016. pyfect/schema/model/_toolkit.py +114 -0
  1017. pyfect/schema/model/_uuid.py +69 -0
  1018. pyfect/schema/representation/__init__.py +424 -0
  1019. pyfect/schema/representation/_builtin_revivers.py +331 -0
  1020. pyfect/schema/representation/_code.py +106 -0
  1021. pyfect/schema/representation/_code_builtins.py +281 -0
  1022. pyfect/schema/representation/_code_document.py +563 -0
  1023. pyfect/schema/representation/_code_sort.py +111 -0
  1024. pyfect/schema/representation/_from.py +278 -0
  1025. pyfect/schema/representation/_import/__init__.py +78 -0
  1026. pyfect/schema/representation/_import/_finalize.py +121 -0
  1027. pyfect/schema/representation/_import/_intersect.py +467 -0
  1028. pyfect/schema/representation/_import/_js_order.py +66 -0
  1029. pyfect/schema/representation/_import/_keywords.py +224 -0
  1030. pyfect/schema/representation/_import/_nodes.py +199 -0
  1031. pyfect/schema/representation/_import/_translate.py +455 -0
  1032. pyfect/schema/representation/_json.py +520 -0
  1033. pyfect/schema/representation/_json_schema.py +178 -0
  1034. pyfect/schema/representation/_model.py +284 -0
  1035. pyfect/schema/representation/_revivers.py +89 -0
  1036. pyfect/schema/representation/_to.py +249 -0
  1037. pyfect/schema/transformation/__init__.py +99 -0
  1038. pyfect/schema/transformation/_builtins.py +314 -0
  1039. pyfect/schema/transformation/_durations.py +150 -0
  1040. pyfect/schema/transformation/_transformation.py +177 -0
  1041. pyfect/schema/variant_schema/__init__.py +69 -0
  1042. pyfect/schema/variant_schema/_class.py +188 -0
  1043. pyfect/schema/variant_schema/_defaults.py +30 -0
  1044. pyfect/schema/variant_schema/_field.py +32 -0
  1045. pyfect/schema/variant_schema/_struct.py +130 -0
  1046. pyfect/schema/variant_schema/_toolkit.py +160 -0
  1047. pyfect/scope.py +374 -0
  1048. pyfect/scoped_ref.py +124 -0
  1049. pyfect/semaphore.py +276 -0
  1050. pyfect/semver.py +403 -0
  1051. pyfect/semver_range.py +420 -0
  1052. pyfect/sink/__init__.py +131 -0
  1053. pyfect/sink/_combinators.py +151 -0
  1054. pyfect/sink/_constructors.py +187 -0
  1055. pyfect/sink/_context.py +87 -0
  1056. pyfect/sink/_folds.py +336 -0
  1057. pyfect/sink/_for_each.py +87 -0
  1058. pyfect/sink/_sink.py +265 -0
  1059. pyfect/sink/_steps.py +34 -0
  1060. pyfect/sink/_taking.py +183 -0
  1061. pyfect/socket/__init__.py +146 -0
  1062. pyfect/socket/_accepted.py +65 -0
  1063. pyfect/socket/_connection.py +384 -0
  1064. pyfect/socket/_errors.py +135 -0
  1065. pyfect/socket/_loop.py +66 -0
  1066. pyfect/socket/_net.py +281 -0
  1067. pyfect/socket/_socket.py +245 -0
  1068. pyfect/socket/_tls.py +88 -0
  1069. pyfect/socket/_tls_session.py +117 -0
  1070. pyfect/socket/_web_socket.py +555 -0
  1071. pyfect/socket/datagram/__init__.py +110 -0
  1072. pyfect/socket/datagram/_core.py +460 -0
  1073. pyfect/socket/datagram/_errors.py +102 -0
  1074. pyfect/socket/datagram/_live.py +675 -0
  1075. pyfect/socket/datagram/_model.py +217 -0
  1076. pyfect/socket/server.py +258 -0
  1077. pyfect/socket/ws.py +484 -0
  1078. pyfect/sql/__init__.py +60 -0
  1079. pyfect/sql/_loop.py +134 -0
  1080. pyfect/sql/clickhouse/__init__.py +74 -0
  1081. pyfect/sql/clickhouse/_connection.py +237 -0
  1082. pyfect/sql/clickhouse/_rows.py +87 -0
  1083. pyfect/sql/clickhouse/client.py +322 -0
  1084. pyfect/sql/clickhouse/config.py +241 -0
  1085. pyfect/sql/clickhouse/driver.py +434 -0
  1086. pyfect/sql/clickhouse/error.py +106 -0
  1087. pyfect/sql/clickhouse/migrator.py +47 -0
  1088. pyfect/sql/clickhouse/params.py +168 -0
  1089. pyfect/sql/client/__init__.py +35 -0
  1090. pyfect/sql/client/_client.py +309 -0
  1091. pyfect/sql/client/_sqlite_acquirers.py +130 -0
  1092. pyfect/sql/client/_transaction.py +410 -0
  1093. pyfect/sql/connection.py +87 -0
  1094. pyfect/sql/d1/__init__.py +48 -0
  1095. pyfect/sql/d1/api.py +270 -0
  1096. pyfect/sql/d1/client.py +344 -0
  1097. pyfect/sql/d1/config.py +55 -0
  1098. pyfect/sql/error.py +311 -0
  1099. pyfect/sql/libsql/__init__.py +58 -0
  1100. pyfect/sql/libsql/_connection.py +110 -0
  1101. pyfect/sql/libsql/_hrana.py +258 -0
  1102. pyfect/sql/libsql/_local.py +251 -0
  1103. pyfect/sql/libsql/_targets.py +28 -0
  1104. pyfect/sql/libsql/_values.py +46 -0
  1105. pyfect/sql/libsql/client.py +225 -0
  1106. pyfect/sql/libsql/config.py +170 -0
  1107. pyfect/sql/libsql/migrator.py +43 -0
  1108. pyfect/sql/libsql/sdk.py +123 -0
  1109. pyfect/sql/migrator.py +434 -0
  1110. pyfect/sql/model.py +324 -0
  1111. pyfect/sql/mssql/__init__.py +79 -0
  1112. pyfect/sql/mssql/_connection.py +169 -0
  1113. pyfect/sql/mssql/_driver.py +103 -0
  1114. pyfect/sql/mssql/_names.py +9 -0
  1115. pyfect/sql/mssql/_pool.py +125 -0
  1116. pyfect/sql/mssql/_tds.py +447 -0
  1117. pyfect/sql/mssql/_tds_params.py +155 -0
  1118. pyfect/sql/mssql/_worker.py +155 -0
  1119. pyfect/sql/mssql/client.py +344 -0
  1120. pyfect/sql/mssql/config.py +118 -0
  1121. pyfect/sql/mssql/error.py +111 -0
  1122. pyfect/sql/mssql/migrator.py +43 -0
  1123. pyfect/sql/mssql/parameter.py +44 -0
  1124. pyfect/sql/mssql/procedure.py +119 -0
  1125. pyfect/sql/mssql/types.py +137 -0
  1126. pyfect/sql/mysql/__init__.py +60 -0
  1127. pyfect/sql/mysql/_asyncmy.py +168 -0
  1128. pyfect/sql/mysql/_bind.py +76 -0
  1129. pyfect/sql/mysql/_connection.py +156 -0
  1130. pyfect/sql/mysql/_pool.py +63 -0
  1131. pyfect/sql/mysql/_session.py +262 -0
  1132. pyfect/sql/mysql/client.py +244 -0
  1133. pyfect/sql/mysql/config.py +108 -0
  1134. pyfect/sql/mysql/error.py +104 -0
  1135. pyfect/sql/mysql/migrator.py +44 -0
  1136. pyfect/sql/pg/__init__.py +82 -0
  1137. pyfect/sql/pg/_resolve.py +284 -0
  1138. pyfect/sql/pg/_session.py +380 -0
  1139. pyfect/sql/pg/client.py +398 -0
  1140. pyfect/sql/pg/config.py +142 -0
  1141. pyfect/sql/pg/connection.py +585 -0
  1142. pyfect/sql/pg/error.py +131 -0
  1143. pyfect/sql/pg/migrator.py +139 -0
  1144. pyfect/sql/pg/pool.py +265 -0
  1145. pyfect/sql/resolver.py +294 -0
  1146. pyfect/sql/result.py +24 -0
  1147. pyfect/sql/schema.py +116 -0
  1148. pyfect/sql/sqlite/__init__.py +26 -0
  1149. pyfect/sql/sqlite/_connection.py +230 -0
  1150. pyfect/sql/sqlite/_worker.py +71 -0
  1151. pyfect/sql/sqlite/client.py +248 -0
  1152. pyfect/sql/sqlite/migrator.py +43 -0
  1153. pyfect/sql/statement/__init__.py +144 -0
  1154. pyfect/sql/statement/_compiler.py +384 -0
  1155. pyfect/sql/statement/_segments.py +487 -0
  1156. pyfect/sql/statement/_statement.py +526 -0
  1157. pyfect/sql/statement/_transforms.py +93 -0
  1158. pyfect/sql/stream.py +88 -0
  1159. pyfect/stdio.py +206 -0
  1160. pyfect/stream/__init__.py +151 -0
  1161. pyfect/stream/_async_iterable.py +129 -0
  1162. pyfect/stream/_base.py +69 -0
  1163. pyfect/stream/_bridge_methods.py +139 -0
  1164. pyfect/stream/_catch.py +135 -0
  1165. pyfect/stream/_catch_reason.py +558 -0
  1166. pyfect/stream/_channel_methods.py +61 -0
  1167. pyfect/stream/_chunk_methods.py +169 -0
  1168. pyfect/stream/_chunking.py +310 -0
  1169. pyfect/stream/_concat.py +65 -0
  1170. pyfect/stream/_constructors.py +341 -0
  1171. pyfect/stream/_context.py +147 -0
  1172. pyfect/stream/_error_methods.py +254 -0
  1173. pyfect/stream/_execution_plan.py +199 -0
  1174. pyfect/stream/_fanout.py +154 -0
  1175. pyfect/stream/_fanout_methods.py +183 -0
  1176. pyfect/stream/_filter_methods.py +203 -0
  1177. pyfect/stream/_grouping.py +168 -0
  1178. pyfect/stream/_many.py +65 -0
  1179. pyfect/stream/_map_methods.py +233 -0
  1180. pyfect/stream/_mapping.py +126 -0
  1181. pyfect/stream/_run.py +114 -0
  1182. pyfect/stream/_run_methods.py +114 -0
  1183. pyfect/stream/_service_methods.py +36 -0
  1184. pyfect/stream/_sink_methods.py +151 -0
  1185. pyfect/stream/_sinking.py +277 -0
  1186. pyfect/stream/_sources.py +124 -0
  1187. pyfect/stream/_stream.py +162 -0
  1188. pyfect/stream/_taking.py +330 -0
  1189. pyfect/stream/_text.py +43 -0
  1190. pyfect/stream/_timing.py +294 -0
  1191. pyfect/stream/_timing_methods.py +252 -0
  1192. pyfect/stream/_zip_methods.py +279 -0
  1193. pyfect/stream/_zipping.py +213 -0
  1194. pyfect/subscription_ref.py +66 -0
  1195. pyfect/synchronized_ref.py +38 -0
  1196. pyfect/take.py +36 -0
  1197. pyfect/terminal/__init__.py +41 -0
  1198. pyfect/terminal/_keys.py +113 -0
  1199. pyfect/terminal/_live.py +223 -0
  1200. pyfect/terminal/_service.py +73 -0
  1201. pyfect/testing/__init__.py +122 -0
  1202. pyfect/testing/_assertions.py +92 -0
  1203. pyfect/testing/_clock.py +309 -0
  1204. pyfect/testing/_console.py +164 -0
  1205. pyfect/testing/_crypto.py +44 -0
  1206. pyfect/testing/_decorators.py +211 -0
  1207. pyfect/testing/_flaky.py +34 -0
  1208. pyfect/testing/_layers.py +153 -0
  1209. pyfect/testing/_prop.py +255 -0
  1210. pyfect/testing/_runners.py +84 -0
  1211. pyfect/testing/_runtime.py +174 -0
  1212. pyfect/testing/_schema.py +238 -0
  1213. pyfect/testing/plugin.py +59 -0
  1214. pyfect/tracer/__init__.py +107 -0
  1215. pyfect/tracer/_model.py +176 -0
  1216. pyfect/tracer/_native.py +172 -0
  1217. pyfect/tracer/_refs.py +87 -0
  1218. pyfect/tracer/_spans.py +269 -0
  1219. pyfect/transaction.py +340 -0
  1220. pyfect/trie.py +317 -0
  1221. pyfect/tx_chunk.py +127 -0
  1222. pyfect/tx_deferred.py +75 -0
  1223. pyfect/tx_hash_map.py +265 -0
  1224. pyfect/tx_hash_set.py +151 -0
  1225. pyfect/tx_priority_queue.py +224 -0
  1226. pyfect/tx_pubsub.py +194 -0
  1227. pyfect/tx_queue/__init__.py +27 -0
  1228. pyfect/tx_queue/_queue.py +359 -0
  1229. pyfect/tx_queue/_views.py +60 -0
  1230. pyfect/tx_reentrant_lock.py +201 -0
  1231. pyfect/tx_ref.py +79 -0
  1232. pyfect/tx_semaphore.py +136 -0
  1233. pyfect/tx_subscription_ref.py +118 -0
  1234. pyfect/workers/__init__.py +62 -0
  1235. pyfect/workers/_frames.py +82 -0
  1236. pyfect/workers/_mailbox.py +51 -0
  1237. pyfect/workers/_port.py +106 -0
  1238. pyfect/workers/_process.py +155 -0
  1239. pyfect/workers/_thread.py +100 -0
  1240. pyfect/workers/python_worker.py +133 -0
  1241. pyfect/workers/python_worker_runner.py +112 -0
  1242. pyfect/workers/transferable.py +103 -0
  1243. pyfect/workers/worker.py +273 -0
  1244. pyfect/workers/worker_error.py +86 -0
  1245. pyfect/workers/worker_runner.py +85 -0
  1246. pyfect/workflow/__init__.py +78 -0
  1247. pyfect/workflow/_crypto.py +23 -0
  1248. pyfect/workflow/_encoded.py +381 -0
  1249. pyfect/workflow/_memory.py +424 -0
  1250. pyfect/workflow/_result.py +43 -0
  1251. pyfect/workflow/_services.py +241 -0
  1252. pyfect/workflow/_suspension.py +153 -0
  1253. pyfect/workflow/activity.py +427 -0
  1254. pyfect/workflow/durable_clock.py +72 -0
  1255. pyfect/workflow/durable_deferred.py +403 -0
  1256. pyfect/workflow/durable_queue.py +259 -0
  1257. pyfect/workflow/workflow.py +509 -0
  1258. pyfect/workflow/workflow_engine.py +42 -0
  1259. pyfect/workflow/workflow_proxy.py +161 -0
  1260. pyfect/workflow/workflow_proxy_server.py +146 -0
  1261. pyfect_dev-0.1.0.dist-info/METADATA +296 -0
  1262. pyfect_dev-0.1.0.dist-info/RECORD +1265 -0
  1263. pyfect_dev-0.1.0.dist-info/WHEEL +4 -0
  1264. pyfect_dev-0.1.0.dist-info/entry_points.txt +3 -0
  1265. pyfect_dev-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,841 @@
1
+ """Runner storage in a SQL database (Effect's ``SqlRunnerStorage``).
2
+
3
+ Runners are rows of ``<prefix>_runners``. Shard locks are PostgreSQL or
4
+ MySQL advisory locks held by one reserved connection (rebuilt when a
5
+ statement on it fails), or, on SQLite and SQL Server (or with
6
+ ``shard_lock_disable_advisory``), leases in ``<prefix>_locks`` that
7
+ expire after ``shard_lock_expiration``. Every lock operation has a
8
+ deadline of the effective lock refresh interval.
9
+
10
+ MySQL and SQL Server statements are ported but not run by the tests.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import math
16
+ import random
17
+ from collections.abc import Callable, Generator, Sequence
18
+ from typing import Any, Never, cast
19
+
20
+ from pyfect import layer as layer_
21
+ from pyfect._core import Effect
22
+ from pyfect._fiber import Fiber
23
+ from pyfect._js import text_encode
24
+ from pyfect.cause import Cause
25
+ from pyfect.cluster._resource_ref import ResourceRef
26
+ from pyfect.cluster._shard_lock import effective_interval
27
+ from pyfect.cluster.cluster_error import PersistenceError
28
+ from pyfect.cluster.runner_storage import (
29
+ RunnerStorage,
30
+ RunnerStorageService,
31
+ make_encoded,
32
+ )
33
+ from pyfect.cluster.sharding_config import ShardingConfig, ShardingConfigService, shard_group_config
34
+ from pyfect.duration import Seconds
35
+ from pyfect.effect import gen, provide_service, suspend, void
36
+ from pyfect.exit import Exit
37
+ from pyfect.scope import CloseableScope, Scope, fork_in
38
+ from pyfect.sql import migrator
39
+ from pyfect.sql.client import SqlClient, SqlClientService
40
+ from pyfect.sql.connection import Connection
41
+ from pyfect.sql.error import SqlError
42
+ from pyfect.sql.statement import Fragment, Statement
43
+
44
+
45
+ def postgres_lock_namespace(prefix: str) -> int:
46
+ """The advisory-lock namespace of ``prefix``: FNV-1a of
47
+ ``effect-cluster:<prefix>`` as a signed 32-bit integer. A persistent
48
+ wire format shared with Effect: it must never change."""
49
+ h = 0x811C9DC5
50
+ for byte in text_encode(f"effect-cluster:{prefix}"):
51
+ h = ((h ^ byte) * 0x01000193) & 0xFFFFFFFF
52
+ return h - 0x1_0000_0000 if h >= 0x8000_0000 else h
53
+
54
+
55
+ type _Held = tuple[Connection, int]
56
+
57
+
58
+ class _Storage:
59
+ """The ``EncodedRunnerStorage`` of one client."""
60
+
61
+ def __init__(
62
+ self,
63
+ *,
64
+ sql: SqlClientService,
65
+ config: ShardingConfigService,
66
+ prefix: str,
67
+ layer_scope: CloseableScope,
68
+ lock_conn: ResourceRef[_Held] | None,
69
+ disable_advisory: bool,
70
+ ) -> None:
71
+ self.sql = sql
72
+ self.prefix = prefix
73
+ self.layer_scope = layer_scope
74
+ self.lock_conn = lock_conn
75
+ self.disable_advisory = disable_advisory
76
+ self.deadline = effective_interval(config)
77
+ self.lock_namespace = postgres_lock_namespace(prefix)
78
+ # pg_locks shows the signed int4 key as an unsigned oid
79
+ self.lock_namespace_oid = self.lock_namespace & 0xFFFFFFFF
80
+ self.runners = sql.identifier(f"{prefix}_runners")
81
+ self.locks = sql.identifier(f"{prefix}_locks")
82
+ self.now: Fragment = sql.on_dialect_or_else(
83
+ pg=lambda: sql.literal("NOW()"),
84
+ mysql=lambda: sql.literal("NOW()"),
85
+ mssql=lambda: sql.literal("GETDATE()"),
86
+ or_else=lambda: sql.literal("CURRENT_TIMESTAMP"),
87
+ )
88
+ self.pg_lock_order = sql.literal('ORDER BY shard_id COLLATE "C"')
89
+ seconds = sql.unsafe(str(math.ceil(config.shard_lock_expiration)))
90
+ self.expires_seconds = seconds
91
+ now = self.now
92
+ self.lock_expires_at: Fragment = sql.on_dialect_or_else(
93
+ pg=lambda: sql("{} - INTERVAL '{} seconds'", now, seconds),
94
+ mysql=lambda: sql("DATE_SUB({}, INTERVAL {} SECOND)", now, seconds),
95
+ mssql=lambda: sql("DATEADD(SECOND, -{}, {})", seconds, now),
96
+ or_else=lambda: sql("datetime({}, '-{} seconds')", now, seconds),
97
+ )
98
+ self.lock_numbers: dict[str, int] = {}
99
+ self.shard_index: dict[str, int] = {}
100
+ self.lock_names: dict[str, str] = {}
101
+ index = 0
102
+ for i, group in enumerate(shard_group_config(config).available):
103
+ base = (i + 1) * 1_000_000
104
+ for shard in range(1, config.shards_per_group + 1):
105
+ shard_id = f"{group}:{shard}"
106
+ self.lock_numbers[shard_id] = base + shard
107
+ self.shard_index[shard_id] = index
108
+ self.lock_names[shard_id] = f"{prefix}.{shard_id}"
109
+ index += 1
110
+
111
+ # --- helpers ----------------------------------------------------------------------
112
+
113
+ def bool_(self, value: bool) -> object:
114
+ return self.sql.on_dialect_or_else(
115
+ mssql=lambda: int(value), sqlite=lambda: int(value), or_else=lambda: value
116
+ )
117
+
118
+ def wrap_string(self, text: str) -> str:
119
+ return self.sql.on_dialect_or_else(mssql=lambda: f"N'{text}'", or_else=lambda: f"'{text}'")
120
+
121
+ def string_literal(self, text: str) -> Statement[Any]:
122
+ return self.sql.unsafe(self.wrap_string(text))
123
+
124
+ def string_list(self, texts: Sequence[str]) -> Statement[Any]:
125
+ return self.sql.unsafe("(" + ",".join(self.wrap_string(t) for t in texts) + ")")
126
+
127
+ def with_deadline[A, E](self, operation: Effect[A, E]) -> Effect[A, E]:
128
+ """``operation`` on a fiber of the layer's scope, joined with the
129
+ deadline: an operation stuck in an uninterruptible region (closing
130
+ an unresponsive connection) finishes detached."""
131
+
132
+ def body() -> Generator[Effect[Any, Any, Any], Any, A]:
133
+ fiber: Fiber[A, E] = yield from fork_in(
134
+ operation, self.layer_scope, start_immediately=True
135
+ )
136
+
137
+ def stop() -> Effect[None]:
138
+ if fiber.poll_unsafe() is not None:
139
+ return void
140
+ return fork_in(
141
+ fiber.interrupt(), self.layer_scope, start_immediately=True
142
+ ).as_void()
143
+
144
+ return (yield from fiber.join().timeout(self.deadline).ensuring(suspend(stop)))
145
+
146
+ return gen(body)
147
+
148
+ def rebuild_lock_conn(self, from_: _Held | None = None) -> Effect[None]:
149
+ rebuild = None if self.lock_conn is None else self.lock_conn.rebuild_unsafe(from_=from_)
150
+ if rebuild is None:
151
+ return void
152
+ # A stalled release must not block later rebuilds.
153
+ return fork_in(
154
+ self.with_deadline(rebuild).exit(), self.layer_scope, start_immediately=True
155
+ ).as_void()
156
+
157
+ @staticmethod
158
+ def _rebuild_on_failure[A, E](
159
+ operation: Effect[A, E], rebuild: Callable[[], Effect[None]]
160
+ ) -> Effect[A, E]:
161
+ """``operation``, running ``rebuild`` when it fails. An interrupt
162
+ is not a broken connection: a rebuild on shutdown can lose its
163
+ ``pg_advisory_unlock_all`` and pool a session holding the locks."""
164
+
165
+ def failed(cause: Cause[Any]) -> Effect[None]:
166
+ return void if cause.has_interrupts_only else rebuild()
167
+
168
+ return operation.on_error(failed)
169
+
170
+ def use_lock_conn[A, E](self, f: Callable[[Connection, int], Effect[A, E]]) -> Effect[A, E]:
171
+ """``f`` on the reserved connection (PostgreSQL and MySQL, which
172
+ always have one); a failure replaces it."""
173
+ lock_conn = cast("ResourceRef[_Held]", self.lock_conn)
174
+
175
+ def run(held: _Held) -> Effect[A, E]:
176
+ return self._rebuild_on_failure(
177
+ f(held[0], held[1]), lambda: self.rebuild_lock_conn(held)
178
+ )
179
+
180
+ return lock_conn.await_.flat_map(run)
181
+
182
+ def exec(self, statement: Statement[Any]) -> Effect[object, SqlError]:
183
+ if self.lock_conn is None:
184
+ return statement.raw
185
+ query, params = statement.compile()
186
+ return self.use_lock_conn(lambda conn, _pid: conn.execute_raw(query, params))
187
+
188
+ def exec_values(
189
+ self, statement: Statement[Any]
190
+ ) -> Effect[Sequence[tuple[object, ...]], SqlError]:
191
+ if self.lock_conn is None:
192
+ return statement.values
193
+ query, params = statement.compile()
194
+ return self.use_lock_conn(lambda conn, _pid: conn.execute_values(query, params))
195
+
196
+ def exec_unprepared(self, statement: Statement[Any]) -> Effect[Sequence[Any], SqlError]:
197
+ if self.lock_conn is None:
198
+ return statement.values_unprepared
199
+ query, params = statement.compile()
200
+ return self.use_lock_conn(lambda conn, _pid: conn.execute_unprepared(query, params, None))
201
+
202
+ def with_lock_operation_deadline[A, E](self, operation: Effect[A, E]) -> Effect[A, E]:
203
+ """``operation`` with the deadline; on failure the connection ready
204
+ at entry is replaced (or a failed rebuild retried)."""
205
+
206
+ def run() -> Effect[A, E]:
207
+ ready = None if self.lock_conn is None else self.lock_conn.get_unsafe()
208
+
209
+ def rebuild() -> Effect[None]:
210
+ if ready is not None:
211
+ return self.rebuild_lock_conn(ready)
212
+ if self.lock_conn is not None and self.lock_conn.get_unsafe() is None:
213
+ return self.rebuild_lock_conn()
214
+ return void
215
+
216
+ return self._rebuild_on_failure(self.with_deadline(operation), rebuild)
217
+
218
+ return suspend(run)
219
+
220
+ # --- statements -------------------------------------------------------------------
221
+
222
+ def lock_values(self, address: str, shard_ids: Sequence[str]) -> Fragment:
223
+ sql = self.sql
224
+ return sql.csv(
225
+ [
226
+ sql("({}, {}, {})", self.string_literal(s), self.string_literal(address), self.now)
227
+ for s in shard_ids
228
+ ]
229
+ )
230
+
231
+ def acquired_locks(self, address: str, shard_ids: Sequence[str]) -> Effect[list[str], SqlError]:
232
+ statement = self.sql(
233
+ "SELECT shard_id FROM {} WHERE address = {} AND acquired_at >= {} AND shard_id IN {}",
234
+ self.locks,
235
+ address,
236
+ self.lock_expires_at,
237
+ self.string_list(shard_ids),
238
+ )
239
+ return self.exec_values(statement).map(lambda rows: [str(r[0]) for r in rows])
240
+
241
+ def acquire_lock(self, address: str, shard_ids: Sequence[str]) -> Effect[list[str], SqlError]:
242
+ sql = self.sql
243
+ if sql.dialect == "pg":
244
+ if self.disable_advisory:
245
+ statement = sql(
246
+ "INSERT INTO {} (shard_id, address, acquired_at) "
247
+ "SELECT * FROM (VALUES {}) AS requested (shard_id, address, acquired_at) {} "
248
+ "ON CONFLICT (shard_id) DO UPDATE SET address = {}, acquired_at = {} "
249
+ "WHERE {}.address = {} OR {}.acquired_at < {}",
250
+ self.locks,
251
+ self.lock_values(address, shard_ids),
252
+ self.pg_lock_order,
253
+ address,
254
+ self.now,
255
+ self.locks,
256
+ address,
257
+ self.locks,
258
+ self.lock_expires_at,
259
+ )
260
+ return self.exec(statement).flat_map(
261
+ lambda _: self.acquired_locks(address, shard_ids)
262
+ )
263
+ return self.use_lock_conn(lambda conn, pid: self._pg_try_locks(conn, pid, shard_ids))
264
+ if sql.dialect == "mysql":
265
+ if self.disable_advisory:
266
+ statement = sql(
267
+ "INSERT INTO {} (shard_id, address, acquired_at) VALUES {} "
268
+ "ON DUPLICATE KEY UPDATE "
269
+ "address = IF(address = VALUES(address) OR acquired_at < {}, "
270
+ "VALUES(address), address), "
271
+ "acquired_at = IF(address = VALUES(address) OR acquired_at < {}, "
272
+ "VALUES(acquired_at), acquired_at)",
273
+ self.locks,
274
+ self.lock_values(address, shard_ids),
275
+ self.lock_expires_at,
276
+ self.lock_expires_at,
277
+ )
278
+ return self.exec_unprepared(statement).flat_map(
279
+ lambda _: self.acquired_locks(address, shard_ids)
280
+ )
281
+ return self.use_lock_conn(lambda conn, pid: self._mysql_get_locks(conn, pid, shard_ids))
282
+ if sql.dialect == "mssql":
283
+ statement = sql(
284
+ "MERGE {} WITH (HOLDLOCK) AS target "
285
+ "USING (SELECT * FROM (VALUES {})) AS source (shard_id, address, acquired_at) "
286
+ "ON target.shard_id = source.shard_id "
287
+ "WHEN MATCHED AND (target.address = source.address OR "
288
+ "DATEDIFF(SECOND, target.acquired_at, {}) > {}) THEN "
289
+ "UPDATE SET address = source.address, acquired_at = source.acquired_at "
290
+ "WHEN NOT MATCHED THEN INSERT (shard_id, address, acquired_at) "
291
+ "VALUES (source.shard_id, source.address, source.acquired_at);",
292
+ self.locks,
293
+ self.lock_values(address, shard_ids),
294
+ self.now,
295
+ self.expires_seconds,
296
+ )
297
+ return sql.with_transaction(
298
+ statement.flat_map(lambda _: self.acquired_locks(address, shard_ids))
299
+ )
300
+ statement = sql(
301
+ "WITH source(shard_id, address, acquired_at) AS (VALUES {}) "
302
+ "INSERT INTO {} (shard_id, address, acquired_at) "
303
+ "SELECT source.shard_id, source.address, source.acquired_at FROM source "
304
+ "WHERE NOT EXISTS (SELECT 1 FROM {} WHERE shard_id = source.shard_id "
305
+ "AND address != {} "
306
+ "AND (strftime('%s', {}) - strftime('%s', acquired_at)) <= {}) "
307
+ "ON CONFLICT(shard_id) DO UPDATE SET address = {}, acquired_at = {}",
308
+ self.lock_values(address, shard_ids),
309
+ self.locks,
310
+ self.locks,
311
+ address,
312
+ self.now,
313
+ self.expires_seconds,
314
+ address,
315
+ self.now,
316
+ )
317
+ return sql.with_transaction(
318
+ statement.flat_map(lambda _: self.acquired_locks(address, shard_ids))
319
+ )
320
+
321
+ def _pg_try_locks(
322
+ self, conn: Connection, pid: int, shard_ids: Sequence[str]
323
+ ) -> Effect[list[str], SqlError]:
324
+ def body() -> Generator[Effect[Any, Any, Any], Any, list[str]]:
325
+ acquired: list[str] = []
326
+ to_acquire = {self.lock_numbers[s]: s for s in shard_ids}
327
+ taken = yield from conn.execute_values(
328
+ "SELECT objid FROM pg_locks WHERE locktype = 'advisory' AND granted = true "
329
+ f"AND classid = {self.lock_namespace_oid} AND objsubid = 2 AND pid = {pid} "
330
+ "ORDER BY objid",
331
+ [],
332
+ )
333
+ for lock_number, *_rest in taken:
334
+ shard_id = to_acquire.pop(int(str(lock_number)), None)
335
+ if shard_id is not None:
336
+ acquired.append(shard_id)
337
+ if not to_acquire:
338
+ return acquired
339
+ locks = ", ".join(
340
+ f'pg_try_advisory_lock({self.lock_namespace}, {number}) AS "{shard_id}"'
341
+ for number, shard_id in to_acquire.items()
342
+ )
343
+ rows = yield from conn.execute_unprepared(f"SELECT {locks}", [], None)
344
+ for shard_id, got in rows[0].items():
345
+ if got:
346
+ acquired.append(shard_id)
347
+ return acquired
348
+
349
+ return gen(body)
350
+
351
+ def _mysql_get_locks(
352
+ self, conn: Connection, pid: int, shard_ids: Sequence[str]
353
+ ) -> Effect[list[str], SqlError]:
354
+ def body() -> Generator[Effect[Any, Any, Any], Any, list[str]]:
355
+ all_taken = ", ".join(
356
+ f"IS_USED_LOCK('{name}') AS \"{shard_id}\""
357
+ for shard_id, name in self.lock_names.items()
358
+ )
359
+ taken = (yield from conn.execute_values(f"SELECT {all_taken}", []))[0]
360
+ acquired: list[str] = []
361
+ to_acquire: list[str] = []
362
+ for shard_id in shard_ids:
363
+ if taken[self.shard_index[shard_id]] == pid:
364
+ acquired.append(shard_id)
365
+ else:
366
+ to_acquire.append(shard_id)
367
+ if not to_acquire:
368
+ return acquired
369
+ locks = ", ".join(f"GET_LOCK('{self.lock_names[s]}', 0) AS \"{s}\"" for s in to_acquire)
370
+ results = (yield from conn.execute_values(f"SELECT {locks}", []))[0]
371
+ for shard_id, result in zip(to_acquire, results, strict=True):
372
+ if result == 1:
373
+ acquired.append(shard_id)
374
+ return acquired
375
+
376
+ return gen(body)
377
+
378
+ def refresh_shards(self, address: str, shard_ids: Sequence[str]) -> Effect[list[str], SqlError]:
379
+ sql = self.sql
380
+ shards = self.string_list(shard_ids)
381
+
382
+ def first_column(rows: Sequence[Any]) -> list[str]:
383
+ return [str(r[0]) for r in rows]
384
+
385
+ if sql.dialect == "pg":
386
+ if not self.disable_advisory:
387
+ return self.acquire_lock(address, shard_ids)
388
+ statement = sql(
389
+ "WITH locked AS (SELECT shard_id FROM {} WHERE address = {} AND shard_id IN {} "
390
+ "{} FOR UPDATE) "
391
+ "UPDATE {} SET acquired_at = {} "
392
+ "WHERE address = {} AND shard_id IN (SELECT shard_id FROM locked) "
393
+ "RETURNING shard_id",
394
+ self.locks,
395
+ address,
396
+ shards,
397
+ self.pg_lock_order,
398
+ self.locks,
399
+ self.now,
400
+ address,
401
+ )
402
+ return self.exec_values(statement).map(first_column)
403
+ if sql.dialect == "mysql":
404
+ if not self.disable_advisory:
405
+ return self.acquire_lock(address, shard_ids)
406
+ statement = sql(
407
+ "UPDATE {} SET acquired_at = {} WHERE address = {} AND shard_id IN {}; "
408
+ "SELECT shard_id FROM {} WHERE address = {} AND shard_id IN {}",
409
+ self.locks,
410
+ self.now,
411
+ address,
412
+ shards,
413
+ self.locks,
414
+ address,
415
+ shards,
416
+ )
417
+ return self.exec_unprepared(statement).map(
418
+ lambda results: [str(row["shard_id"]) for row in results[1]]
419
+ )
420
+ if sql.dialect == "mssql":
421
+ statement = sql(
422
+ "UPDATE {} SET acquired_at = {} OUTPUT inserted.shard_id "
423
+ "WHERE address = {} AND shard_id IN {}",
424
+ self.locks,
425
+ self.now,
426
+ address,
427
+ shards,
428
+ )
429
+ return self.exec_values(statement).map(first_column)
430
+ statement = sql(
431
+ "UPDATE {} SET acquired_at = {} WHERE address = {} AND shard_id IN {} "
432
+ "RETURNING shard_id",
433
+ self.locks,
434
+ self.now,
435
+ address,
436
+ shards,
437
+ )
438
+ return self.exec_values(statement).map(first_column)
439
+
440
+ def release_shard(self, address: str, shard_id: str) -> Effect[object, SqlError]:
441
+ sql = self.sql
442
+ delete = sql(
443
+ "DELETE FROM {} WHERE address = {} AND shard_id = {}", self.locks, address, shard_id
444
+ )
445
+ if sql.dialect in ("pg", "mysql") and self.disable_advisory:
446
+ return self.exec(delete)
447
+ if sql.dialect == "pg":
448
+ number = self.lock_numbers[shard_id]
449
+
450
+ def pg(conn: Connection, _pid: int) -> Effect[None, SqlError]:
451
+ def body() -> Generator[Effect[Any, Any, Any], Any]:
452
+ for _ in range(5):
453
+ yield from conn.execute_raw(
454
+ f"SELECT pg_advisory_unlock({self.lock_namespace}, {number})", []
455
+ )
456
+ taken = yield from conn.execute_values(
457
+ "SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND granted = true "
458
+ f"AND classid = {self.lock_namespace_oid} AND objid = {number} "
459
+ "AND objsubid = 2 AND pid = pg_backend_pid()",
460
+ [],
461
+ )
462
+ if not taken:
463
+ return
464
+ yield from conn.execute_raw("SELECT pg_advisory_unlock_all()", [])
465
+
466
+ return gen(body)
467
+
468
+ return self.use_lock_conn(pg)
469
+ if sql.dialect == "mysql":
470
+ name = self.lock_names[shard_id]
471
+
472
+ def mysql(conn: Connection, pid: int) -> Effect[None, SqlError]:
473
+ def body() -> Generator[Effect[Any, Any, Any], Any]:
474
+ while True:
475
+ yield from conn.execute_raw(f"SELECT RELEASE_LOCK('{name}')", [])
476
+ taken = yield from conn.execute_values(f"SELECT IS_USED_LOCK('{name}')", [])
477
+ if not taken or taken[0][0] != pid:
478
+ return
479
+
480
+ return gen(body)
481
+
482
+ return self.use_lock_conn(mysql)
483
+ return delete
484
+
485
+ def release_all_shards(self, address: str) -> Effect[object, SqlError]:
486
+ sql = self.sql
487
+ if sql.dialect == "pg":
488
+ if self.disable_advisory:
489
+ return self.exec(
490
+ sql(
491
+ "WITH locked AS (SELECT shard_id FROM {} WHERE address = {} {} FOR UPDATE) "
492
+ "DELETE FROM {} WHERE address = {} "
493
+ "AND shard_id IN (SELECT shard_id FROM locked)",
494
+ self.locks,
495
+ address,
496
+ self.pg_lock_order,
497
+ self.locks,
498
+ address,
499
+ )
500
+ )
501
+ return self.exec(sql("SELECT pg_advisory_unlock_all()"))
502
+ if sql.dialect == "mysql":
503
+ if self.disable_advisory:
504
+ return self.exec(sql("DELETE FROM {} WHERE address = {}", self.locks, address))
505
+ return self.exec(sql("SELECT RELEASE_ALL_LOCKS()"))
506
+ return sql("DELETE FROM {} WHERE address = {}", self.locks, address)
507
+
508
+ def insert_runner(
509
+ self, address: str, runner: str, healthy: bool
510
+ ) -> Effect[Sequence[Any], SqlError]:
511
+ sql = self.sql
512
+ if sql.dialect == "mssql":
513
+ return sql(
514
+ "MERGE {} AS target USING (SELECT {} AS address, {} AS runner, "
515
+ "{} AS last_heartbeat, {} AS healthy) AS source ON target.address = source.address "
516
+ "WHEN MATCHED THEN UPDATE SET runner = source.runner, "
517
+ "last_heartbeat = source.last_heartbeat, healthy = source.healthy "
518
+ "WHEN NOT MATCHED THEN INSERT (address, runner, last_heartbeat, healthy) "
519
+ "VALUES (source.address, source.runner, source.last_heartbeat, source.healthy) "
520
+ "OUTPUT INSERTED.machine_id;",
521
+ self.runners,
522
+ address,
523
+ runner,
524
+ self.now,
525
+ self.bool_(healthy),
526
+ ).values
527
+ if sql.dialect == "mysql":
528
+ return sql(
529
+ "INSERT INTO {} (address, runner, last_heartbeat, healthy) VALUES ({}, {}, {}, {}) "
530
+ "ON DUPLICATE KEY UPDATE runner = VALUES(runner), "
531
+ "last_heartbeat = VALUES(last_heartbeat), healthy = VALUES(healthy); "
532
+ "SELECT machine_id FROM {} WHERE address = {};",
533
+ self.runners,
534
+ address,
535
+ runner,
536
+ self.now,
537
+ healthy,
538
+ self.runners,
539
+ address,
540
+ ).unprepared.map(_mysql_machine_id)
541
+ if sql.dialect == "pg":
542
+ return sql(
543
+ "INSERT INTO {} (address, runner, last_heartbeat, healthy) VALUES ({}, {}, {}, {}) "
544
+ "ON CONFLICT (address) DO UPDATE SET runner = EXCLUDED.runner, "
545
+ "last_heartbeat = EXCLUDED.last_heartbeat, healthy = EXCLUDED.healthy "
546
+ "RETURNING machine_id",
547
+ self.runners,
548
+ address,
549
+ runner,
550
+ self.now,
551
+ healthy,
552
+ ).values
553
+ return sql(
554
+ "INSERT INTO {} (address, runner, last_heartbeat, healthy) VALUES ({}, {}, {}, {}) "
555
+ "ON CONFLICT(address) DO UPDATE SET runner = excluded.runner, "
556
+ "last_heartbeat = excluded.last_heartbeat, healthy = excluded.healthy "
557
+ "RETURNING machine_id;",
558
+ self.runners,
559
+ address,
560
+ runner,
561
+ self.now,
562
+ self.bool_(healthy),
563
+ ).values
564
+
565
+ # --- EncodedRunnerStorage ---------------------------------------------------------
566
+
567
+ @property
568
+ def get_runners(self) -> Effect[list[tuple[str, bool]], PersistenceError]:
569
+ statement = self.sql(
570
+ "SELECT runner, healthy FROM {} WHERE last_heartbeat > {}",
571
+ self.runners,
572
+ self.lock_expires_at,
573
+ )
574
+ return PersistenceError.refail(
575
+ statement.values.map(lambda rows: [(str(r[0]), bool(r[1])) for r in rows])
576
+ )
577
+
578
+ def register(
579
+ self, address: str, runner: str, healthy: bool, /
580
+ ) -> Effect[int, PersistenceError]:
581
+ return PersistenceError.refail(
582
+ self.insert_runner(address, runner, healthy).map(lambda rows: int(rows[0][0]))
583
+ )
584
+
585
+ def unregister(self, address: str, /) -> Effect[None, PersistenceError]:
586
+ statement = self.sql(
587
+ "DELETE FROM {} WHERE address = {} OR last_heartbeat < {}",
588
+ self.runners,
589
+ address,
590
+ self.lock_expires_at,
591
+ )
592
+ return PersistenceError.refail(statement.as_void())
593
+
594
+ def set_runner_health(self, address: str, healthy: bool, /) -> Effect[None, PersistenceError]:
595
+ statement = self.sql(
596
+ "UPDATE {} SET healthy = {} WHERE address = {}",
597
+ self.runners,
598
+ self.bool_(healthy),
599
+ address,
600
+ )
601
+ return PersistenceError.refail(statement.as_void())
602
+
603
+ def acquire(
604
+ self, address: str, shard_ids: Sequence[str], /
605
+ ) -> Effect[list[str], PersistenceError]:
606
+ return PersistenceError.refail(
607
+ self.with_lock_operation_deadline(self.acquire_lock(address, shard_ids))
608
+ )
609
+
610
+ def refresh(
611
+ self, address: str, shard_ids: Sequence[str], /
612
+ ) -> Effect[list[str], PersistenceError]:
613
+ heartbeat = self.sql(
614
+ "UPDATE {} SET last_heartbeat = {} WHERE address = {}", self.runners, self.now, address
615
+ )
616
+ if not shard_ids:
617
+ # The liveness probe while lock storage is unhealthy: on the
618
+ # shared pool, so a wedged reserved connection cannot block it.
619
+ return PersistenceError.refail(self.with_deadline(heartbeat).as_([]))
620
+ return PersistenceError.refail(
621
+ self.with_lock_operation_deadline(
622
+ self.exec(heartbeat).flat_map(lambda _: self.refresh_shards(address, shard_ids))
623
+ )
624
+ )
625
+
626
+ def release(self, address: str, shard_id: str, /) -> Effect[None, PersistenceError]:
627
+ return PersistenceError.refail(
628
+ self.with_lock_operation_deadline(self.release_shard(address, shard_id)).as_void()
629
+ )
630
+
631
+ def release_all(self, address: str, /) -> Effect[None, PersistenceError]:
632
+ return PersistenceError.refail(
633
+ self.with_lock_operation_deadline(self.release_all_shards(address)).as_void()
634
+ )
635
+
636
+
637
+ def _mysql_machine_id(results: Sequence[Any]) -> list[list[object]]:
638
+ """The machine id of a MySQL multi-statement upsert's second result."""
639
+ return [[results[1][0]["machine_id"]]]
640
+
641
+
642
+ def _acquire_lock_conn(
643
+ sql: SqlClientService, disable_advisory: bool, deadline: Seconds
644
+ ) -> Callable[[CloseableScope], Effect[_Held]] | None:
645
+ """How to reserve the lock connection (PostgreSQL and MySQL only)."""
646
+
647
+ def unlock_on_close(scope: CloseableScope, conn: Connection, query: str) -> Effect[None]:
648
+ def finalizer(_: Exit[Any, Any]) -> Effect[None]:
649
+ return conn.execute_raw(query, []).timeout(deadline).interruptible().ignore_cause()
650
+
651
+ return scope.add_finalizer(finalizer)
652
+
653
+ def reserve(scope: CloseableScope) -> Effect[Connection]:
654
+ return sql.reserve.or_die().pipe(provide_service(Scope, scope))
655
+
656
+ def pg(scope: CloseableScope) -> Effect[_Held]:
657
+ def body() -> Generator[Effect[Any, Any, Any], Any, _Held]:
658
+ conn = yield from reserve(scope)
659
+ if disable_advisory:
660
+ return (conn, 0)
661
+ pid = (yield from conn.execute_values("SELECT pg_backend_pid()", []))[0][0]
662
+ yield from unlock_on_close(scope, conn, "SELECT pg_advisory_unlock_all()")
663
+ return (conn, int(str(pid)))
664
+
665
+ return gen(body).or_die()
666
+
667
+ def mysql(scope: CloseableScope) -> Effect[_Held]:
668
+ def body() -> Generator[Effect[Any, Any, Any], Any, _Held]:
669
+ conn = yield from reserve(scope)
670
+ if disable_advisory:
671
+ return (conn, 0)
672
+ # IS_USED_LOCK names the connection id, also under Vitess
673
+ pid: int | None = None
674
+ while pid is None:
675
+ name = f"cluster:pid:{random.randrange(2**53)}"
676
+ taken = (
677
+ yield from conn.execute_values(
678
+ f"SELECT GET_LOCK('{name}', 10), IS_USED_LOCK('{name}')", []
679
+ )
680
+ )[0]
681
+ if taken[0] is None:
682
+ continue
683
+ pid = int(str(taken[1]))
684
+ yield from unlock_on_close(scope, conn, "SELECT RELEASE_ALL_LOCKS()")
685
+ return (conn, pid)
686
+
687
+ return gen(body).or_die()
688
+
689
+ return sql.on_dialect_or_else(pg=lambda: pg, mysql=lambda: mysql, or_else=lambda: None)
690
+
691
+
692
+ def make_storage(
693
+ *, prefix: str = "cluster"
694
+ ) -> Effect[RunnerStorageService, Never, SqlClient | ShardingConfig | Scope]:
695
+ """Runner storage on the tables of ``prefix`` (already migrated)."""
696
+
697
+ def body() -> Generator[Effect[Any, Any, Any], Any, RunnerStorageService]:
698
+ config = yield from ShardingConfig()
699
+ sql = (yield from SqlClient()).without_transforms()
700
+ layer_scope = yield from Scope()
701
+ # PGlite runs every statement on one session: it gets table leases
702
+ # on the shared client.
703
+ single_session = sql.driver == "pglite"
704
+ disable_advisory = config.shard_lock_disable_advisory or single_session
705
+ acquire = (
706
+ None
707
+ if single_session
708
+ else _acquire_lock_conn(sql, disable_advisory, effective_interval(config))
709
+ )
710
+ lock_conn = None
711
+ if acquire is not None:
712
+ lock_conn = yield from ResourceRef.make(layer_scope, acquire)
713
+ storage = _Storage(
714
+ sql=sql,
715
+ config=config,
716
+ prefix=prefix,
717
+ layer_scope=layer_scope,
718
+ lock_conn=lock_conn,
719
+ disable_advisory=disable_advisory,
720
+ )
721
+ return make_encoded(storage)
722
+
723
+ return gen(body).with_tracer_enabled(False)
724
+
725
+
726
+ def migrations(*, prefix: str = "cluster") -> migrator.Loader:
727
+ """The runner tables' migrations (existing deployments may have the
728
+ tables without a migration history: they are created if absent)."""
729
+ runners_table = f"{prefix}_runners"
730
+ locks_table = f"{prefix}_locks"
731
+
732
+ def create() -> Generator[Effect[Any, Any, Any], Any]:
733
+ sql = (yield from SqlClient()).without_transforms()
734
+ runners = sql.identifier(runners_table)
735
+ locks = sql.identifier(locks_table)
736
+ yield from sql.on_dialect_or_else(
737
+ mssql=lambda: sql(
738
+ "IF OBJECT_ID(N'{}', N'U') IS NULL CREATE TABLE {} ("
739
+ "machine_id INT IDENTITY PRIMARY KEY, address VARCHAR(255) NOT NULL, "
740
+ "runner TEXT NOT NULL, healthy BIT NOT NULL DEFAULT 1, "
741
+ "last_heartbeat DATETIME NOT NULL DEFAULT GETDATE(), UNIQUE(address))",
742
+ runners,
743
+ runners,
744
+ ),
745
+ mysql=lambda: sql(
746
+ "CREATE TABLE IF NOT EXISTS {} ("
747
+ "machine_id INT AUTO_INCREMENT PRIMARY KEY, address VARCHAR(255) NOT NULL, "
748
+ "runner TEXT NOT NULL, healthy BOOLEAN NOT NULL DEFAULT TRUE, "
749
+ "last_heartbeat DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(address))",
750
+ runners,
751
+ ),
752
+ pg=lambda: sql(
753
+ "CREATE TABLE IF NOT EXISTS {} ("
754
+ "machine_id SERIAL PRIMARY KEY, address VARCHAR(255) NOT NULL, "
755
+ "runner TEXT NOT NULL, healthy BOOLEAN NOT NULL DEFAULT TRUE, "
756
+ "last_heartbeat TIMESTAMP NOT NULL DEFAULT NOW(), UNIQUE(address))",
757
+ runners,
758
+ ),
759
+ or_else=lambda: sql(
760
+ "CREATE TABLE IF NOT EXISTS {} ("
761
+ "machine_id INTEGER PRIMARY KEY AUTOINCREMENT, address TEXT NOT NULL, "
762
+ "runner TEXT NOT NULL, healthy INTEGER NOT NULL DEFAULT 1, "
763
+ "last_heartbeat DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP), UNIQUE(address))",
764
+ runners,
765
+ ),
766
+ )
767
+ yield from sql.on_dialect_or_else(
768
+ mssql=lambda: sql(
769
+ "IF OBJECT_ID(N'{}', N'U') IS NULL CREATE TABLE {} ("
770
+ "shard_id VARCHAR(50) PRIMARY KEY, address VARCHAR(255) NOT NULL, "
771
+ "acquired_at DATETIME NOT NULL)",
772
+ locks,
773
+ locks,
774
+ ),
775
+ mysql=lambda: sql(
776
+ "CREATE TABLE IF NOT EXISTS {} (shard_id VARCHAR(50) PRIMARY KEY, "
777
+ "address VARCHAR(255) NOT NULL, acquired_at DATETIME NOT NULL)",
778
+ locks,
779
+ ),
780
+ pg=lambda: sql(
781
+ "CREATE TABLE IF NOT EXISTS {} (shard_id VARCHAR(50) PRIMARY KEY, "
782
+ "address VARCHAR(255) NOT NULL, acquired_at TIMESTAMP NOT NULL)",
783
+ locks,
784
+ ),
785
+ or_else=lambda: sql(
786
+ "CREATE TABLE IF NOT EXISTS {} (shard_id TEXT PRIMARY KEY, "
787
+ "address TEXT NOT NULL, acquired_at DATETIME NOT NULL)",
788
+ locks,
789
+ ),
790
+ )
791
+
792
+ return migrator.from_record({"0001_create_tables": gen(create)})
793
+
794
+
795
+ def run_migrations(*, prefix: str = "cluster") -> Effect[None, Never, SqlClient]:
796
+ """Run the runner migrations; a failure is a defect. Message and runner
797
+ migration ids overlap, so they keep separate histories."""
798
+ return (
799
+ migrator.make()(loader=migrations(prefix=prefix), table=f"{prefix}_runner_migrations")
800
+ .as_void()
801
+ .or_die()
802
+ )
803
+
804
+
805
+ def make(
806
+ *, prefix: str = "cluster"
807
+ ) -> Effect[RunnerStorageService, Never, SqlClient | ShardingConfig | Scope]:
808
+ """Run the migrations, then ``make_storage``."""
809
+ return run_migrations(prefix=prefix).flat_map(lambda _: make_storage(prefix=prefix))
810
+
811
+
812
+ def layer_migrations(*, prefix: str = "cluster") -> layer_.Layer[Never, Never, SqlClient]:
813
+ return layer_.effect_discard(run_migrations(prefix=prefix))
814
+
815
+
816
+ def layer_storage(
817
+ *, prefix: str = "cluster"
818
+ ) -> layer_.Layer[RunnerStorage, Never, SqlClient | ShardingConfig]:
819
+ return layer_.effect(RunnerStorage, make_storage(prefix=prefix))
820
+
821
+
822
+ def layer_with(
823
+ *, prefix: str = "cluster"
824
+ ) -> layer_.Layer[RunnerStorage, Never, SqlClient | ShardingConfig]:
825
+ return layer_.effect(RunnerStorage, make(prefix=prefix))
826
+
827
+
828
+ layer: layer_.Layer[RunnerStorage, Never, SqlClient | ShardingConfig] = layer_with()
829
+
830
+ __all__ = [
831
+ "effective_interval",
832
+ "layer",
833
+ "layer_migrations",
834
+ "layer_storage",
835
+ "layer_with",
836
+ "make",
837
+ "make_storage",
838
+ "migrations",
839
+ "postgres_lock_namespace",
840
+ "run_migrations",
841
+ ]