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,1001 @@
1
+ """Message storage in a SQL database (Effect's ``SqlMessageStorage``).
2
+
3
+ Envelopes are rows of ``<prefix>_messages`` and replies rows of
4
+ ``<prefix>_replies``, both keyed by their snowflakes. A request with a
5
+ primary key stores it in ``message_id`` (a SHA-256 digest when longer than
6
+ the 255-character column), which detects duplicates. ``unprocessed_messages``
7
+ claims the rows it returns by setting ``last_read``; a claim lapses after
8
+ ten minutes.
9
+
10
+ MySQL and SQL Server statements are ported but not run by the tests.
11
+ Python's integers are exact, so ids need no ``SafeIntegers``; SHA-256 comes
12
+ from ``hashlib`` (Effect reads a ``Crypto`` service).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import hashlib
18
+ from collections.abc import Generator, Mapping, Sequence
19
+ from typing import Any, Never
20
+
21
+ from pyfect import _json_text
22
+ from pyfect import layer as layer_
23
+ from pyfect._core import Effect
24
+ from pyfect._effect_args import require_effect
25
+ from pyfect._js import js_length, text_encode
26
+ from pyfect.cause import Cause, Fail
27
+ from pyfect.cluster import snowflake as snowflake_
28
+ from pyfect.cluster.cluster_error import PersistenceError
29
+ from pyfect.cluster.entity_address import EntityAddress
30
+ from pyfect.cluster.envelope import Encoded as EnvelopeEncoded
31
+ from pyfect.cluster.envelope import ShardIdEncoded
32
+ from pyfect.cluster.message_storage import (
33
+ SAVE_SUCCESS,
34
+ DuplicateEncoded,
35
+ EncodedMessage,
36
+ EncodedStorage,
37
+ MessageStorage,
38
+ MessageStorageService,
39
+ SaveResultEncoded,
40
+ make_encoded,
41
+ )
42
+ from pyfect.cluster.reply import ChunkEncoded, Encoded, WithExitEncoded
43
+ from pyfect.cluster.sharding_config import ShardingConfig
44
+ from pyfect.cluster.snowflake import Snowflake
45
+ from pyfect.effect import die, fail_cause, gen, succeed, suspend, void
46
+ from pyfect.sql import migrator
47
+ from pyfect.sql.client import SqlClient, SqlClientService
48
+ from pyfect.sql.connection import Row
49
+ from pyfect.sql.error import SqlError, is_sql_error
50
+ from pyfect.sql.result import affected_rows
51
+ from pyfect.sql.statement import Fragment, Statement
52
+
53
+ _REQUEST, _ACK_CHUNK, _INTERRUPT = 0, 1, 2
54
+ _WITH_EXIT = 0
55
+ """A reply's ``kind``: ``0`` for an exit, ``NULL`` for a chunk."""
56
+
57
+ _MESSAGE_ID_WIDTH = 255
58
+
59
+
60
+ def message_id_for_primary_key(primary_key: str) -> str:
61
+ """The stored key: the primary key when it fits the column (in UTF-16
62
+ code units, as Effect measures it), else its
63
+ SHA-256 hex digest (a digest has no ``/``, a composed key always
64
+ does, so the two never collide)."""
65
+ if js_length(primary_key) <= _MESSAGE_ID_WIDTH:
66
+ return primary_key
67
+ return hashlib.sha256(text_encode(primary_key)).hexdigest()
68
+
69
+
70
+ def _shard_id_encoded(text: str) -> ShardIdEncoded:
71
+ group, _, id = text.rpartition(":")
72
+ return {"group": group, "id": int(id)}
73
+
74
+
75
+ def _address(row: Row) -> Any:
76
+ return {
77
+ "shardId": _shard_id_encoded(str(row["shard_id"])),
78
+ "entityType": row["entity_type"],
79
+ "entityId": row["entity_id"],
80
+ }
81
+
82
+
83
+ def _str(value: object) -> str:
84
+ return str(value)
85
+
86
+
87
+ def _reply_from_row(row: Row) -> Encoded:
88
+ if row["kind"] is not None and int(str(row["kind"])) == _WITH_EXIT:
89
+ exit_reply: WithExitEncoded = {
90
+ "_tag": "WithExit",
91
+ "id": _str(row["id"]),
92
+ "requestId": _str(row["request_id"]),
93
+ "exit": _parse(str(row["payload"])),
94
+ }
95
+ return exit_reply
96
+ chunk: ChunkEncoded = {
97
+ "_tag": "Chunk",
98
+ "id": _str(row["id"]),
99
+ "requestId": _str(row["request_id"]),
100
+ "values": _parse(str(row["payload"])),
101
+ "sequence": int(str(row["sequence"])),
102
+ }
103
+ return chunk
104
+
105
+
106
+ def _message_from_row(row: Row) -> EncodedMessage:
107
+ kind = int(str(row["kind"]))
108
+ if kind == _REQUEST:
109
+ envelope: Any = {
110
+ "_tag": "Request",
111
+ "requestId": _str(row["id"]),
112
+ "address": _address(row),
113
+ "tag": row["tag"],
114
+ "payload": _parse(str(row["payload"])),
115
+ "headers": _parse(str(row["headers"])),
116
+ }
117
+ if row["trace_id"]:
118
+ envelope["traceId"] = row["trace_id"]
119
+ envelope["spanId"] = row["span_id"]
120
+ envelope["sampled"] = bool(row["sampled"])
121
+ last: Encoded | None = None
122
+ if row["reply_reply_id"]:
123
+ last = {
124
+ "_tag": "Chunk",
125
+ "id": _str(row["reply_reply_id"]),
126
+ "requestId": _str(row["request_id"]),
127
+ "sequence": int(str(row["reply_sequence"])),
128
+ "values": _parse(str(row["reply_payload"])),
129
+ }
130
+ return EncodedMessage(envelope, last)
131
+ if kind == _ACK_CHUNK:
132
+ ack: Any = {
133
+ "_tag": "AckChunk",
134
+ "id": _str(row["id"]),
135
+ "requestId": _str(row["request_id"]),
136
+ "replyId": _str(row["reply_id"]),
137
+ "address": _address(row),
138
+ }
139
+ return EncodedMessage(ack, None)
140
+ interrupt: Any = {
141
+ "_tag": "Interrupt",
142
+ "id": _str(row["id"]),
143
+ "requestId": _str(row["request_id"]),
144
+ "address": _address(row),
145
+ }
146
+ return EncodedMessage(interrupt, None)
147
+
148
+
149
+ def _group_addresses(
150
+ addresses: Sequence[EntityAddress],
151
+ ) -> list[tuple[str, str, list[str]]]:
152
+ """``(shard, entity type, entity ids)`` groups, in first-seen order."""
153
+ groups: dict[tuple[str, str], list[str]] = {}
154
+ for address in addresses:
155
+ groups.setdefault((str(address.shard_id), address.entity_type), []).append(
156
+ address.entity_id
157
+ )
158
+ return [(shard, entity_type, ids) for (shard, entity_type), ids in groups.items()]
159
+
160
+
161
+ class _Storage:
162
+ """The ``EncodedStorage`` of one client."""
163
+
164
+ def __init__(self, sql: SqlClientService, prefix: str) -> None:
165
+ self.sql = sql
166
+ self.messages = sql.identifier(f"{prefix}_messages")
167
+ self.replies = sql.identifier(f"{prefix}_replies")
168
+ self.supports_booleans: bool = sql.on_dialect_or_else(
169
+ mssql=lambda: False, sqlite=lambda: False, or_else=lambda: True
170
+ )
171
+ self.message_id_enforces_width: bool = sql.on_dialect_or_else(
172
+ mssql=lambda: True, mysql=lambda: True, pg=lambda: True, or_else=lambda: False
173
+ )
174
+ # SQLite ids read as text: drivers that return numbers would round
175
+ self.ids_as_text: bool = sql.dialect == "sqlite"
176
+ self.false = sql.literal("FALSE") if self.supports_booleans else sql.literal("0")
177
+ self.true = sql.literal("TRUE") if self.supports_booleans else sql.literal("1")
178
+ self.kind_ack_chunk = sql.literal("1")
179
+ self.kind_interrupt = sql.literal("2")
180
+ self.kind_with_exit = sql.literal("0")
181
+ self.ten_minutes_ago: Fragment = sql.on_dialect_or_else(
182
+ mssql=lambda: sql.literal("DATEADD(MINUTE, -10, GETDATE())"),
183
+ mysql=lambda: sql.literal("NOW() - INTERVAL 10 MINUTE"),
184
+ pg=lambda: sql.literal("NOW() - INTERVAL '10 minutes'"),
185
+ or_else=lambda: sql.literal("DATETIME('now', '-10 minute')"),
186
+ )
187
+ self.now: Fragment = sql.on_dialect_or_else(
188
+ pg=lambda: sql.literal("NOW()"),
189
+ mysql=lambda: sql.literal("NOW()"),
190
+ mssql=lambda: sql.literal("GETDATE()"),
191
+ or_else=lambda: sql.literal("CURRENT_TIMESTAMP"),
192
+ )
193
+ self.for_update: Fragment = sql.on_dialect_or_else(
194
+ mssql=lambda: sql.literal(""),
195
+ sqlite=lambda: sql.literal(""),
196
+ or_else=lambda: sql.literal("FOR UPDATE"),
197
+ )
198
+ self.update_lock: Fragment = sql.on_dialect_or_else(
199
+ mssql=lambda: sql.literal("WITH (UPDLOCK)"), or_else=lambda: sql.literal("")
200
+ )
201
+ reply_join = (
202
+ "r.kind as reply_kind, r.payload as reply_payload, r.sequence as reply_sequence"
203
+ )
204
+ if self.ids_as_text:
205
+ message_columns = (
206
+ f"{self._id_as('m.id', 'id')}, m.message_id, m.shard_id, m.entity_type, "
207
+ "m.entity_id, m.kind, m.tag, m.payload, m.headers, m.trace_id, m.span_id, "
208
+ f"m.sampled, m.processed, {self._id_as('m.request_id', 'request_id')}, "
209
+ f"{self._id_as('m.reply_id', 'reply_id')}, "
210
+ f"{self._id_as('m.last_reply_id', 'last_reply_id')}, m.last_read, m.deliver_at"
211
+ )
212
+ else:
213
+ message_columns = "m.*"
214
+ self.id_column = sql.unsafe(self._id_as("id", "id"))
215
+ self.reply_columns = sql.unsafe(
216
+ f"{self._id_as('id', 'id')}, kind, {self._id_as('request_id', 'request_id')}, "
217
+ "payload, sequence"
218
+ )
219
+ self.duplicate_columns = sql.unsafe(
220
+ f"{self._id_as('m.id', 'id')}, {self._id_as('r.id', 'reply_id')}, {reply_join}"
221
+ )
222
+ self.message_join_columns = sql.unsafe(
223
+ f"{message_columns}, {self._id_as('r.id', 'reply_reply_id')}, {reply_join}"
224
+ )
225
+
226
+ def _id_as(self, column: str, alias: str) -> str:
227
+ return f"CAST({column} AS TEXT) AS {alias}" if self.ids_as_text else f"{column} AS {alias}"
228
+
229
+ def wrap_string(self, text: str) -> str:
230
+ return f"N'{text}'" if self.sql.dialect == "mssql" else f"'{text}'"
231
+
232
+ def shard_list(self, shard_ids: Sequence[str]) -> Statement[Any]:
233
+ return self.sql.unsafe(",".join(self.wrap_string(s) for s in shard_ids))
234
+
235
+ def may_have_legacy_row(self, primary_key: str) -> bool:
236
+ """SQLite's ``message_id`` stored over-long keys as text before
237
+ digests; those rows are read by the plain key too."""
238
+ return not self.message_id_enforces_width and js_length(primary_key) > _MESSAGE_ID_WIDTH
239
+
240
+ def sampled(self, sampled: bool | None) -> object:
241
+ if sampled is None:
242
+ return None
243
+ return sampled if self.supports_booleans else int(sampled)
244
+
245
+ def envelope_row(
246
+ self, envelope: EnvelopeEncoded, message_id: str | None, deliver_at: int | None
247
+ ) -> dict[str, object]:
248
+ address = envelope["address"]
249
+ shard = f"{address['shardId']['group']}:{address['shardId']['id']}"
250
+ common: dict[str, object] = {
251
+ "message_id": message_id,
252
+ "shard_id": shard,
253
+ "entity_type": address["entityType"],
254
+ "entity_id": address["entityId"],
255
+ "deliver_at": deliver_at,
256
+ }
257
+ match envelope["_tag"]:
258
+ case "Request":
259
+ return {
260
+ "id": envelope["requestId"],
261
+ **common,
262
+ "kind": _REQUEST,
263
+ "tag": envelope["tag"],
264
+ "payload": _json_text.stringify(envelope["payload"]),
265
+ "headers": _json_text.stringify(dict(envelope["headers"])),
266
+ "trace_id": envelope.get("traceId"),
267
+ "span_id": envelope.get("spanId"),
268
+ "sampled": self.sampled(envelope.get("sampled")),
269
+ "request_id": envelope["requestId"],
270
+ "reply_id": None,
271
+ }
272
+ case "AckChunk":
273
+ return {
274
+ "id": envelope["id"],
275
+ **common,
276
+ "kind": _ACK_CHUNK,
277
+ "tag": None,
278
+ "payload": None,
279
+ "headers": None,
280
+ "trace_id": None,
281
+ "span_id": None,
282
+ "sampled": None,
283
+ "request_id": envelope["requestId"],
284
+ "reply_id": envelope["replyId"],
285
+ }
286
+ case "Interrupt":
287
+ return {
288
+ "id": envelope["id"],
289
+ **common,
290
+ "kind": _INTERRUPT,
291
+ "tag": None,
292
+ "payload": None,
293
+ "headers": None,
294
+ "trace_id": None,
295
+ "span_id": None,
296
+ "sampled": None,
297
+ "request_id": envelope["requestId"],
298
+ "reply_id": None,
299
+ }
300
+
301
+ def select_by_message_id(self, message_id: str) -> Effect[Sequence[Row], SqlError]:
302
+ return self.sql(
303
+ "SELECT {} FROM {} m LEFT JOIN {} r ON r.id = m.last_reply_id WHERE m.message_id = {}",
304
+ self.duplicate_columns,
305
+ self.messages,
306
+ self.replies,
307
+ message_id,
308
+ )
309
+
310
+ def insert_envelope(
311
+ self, row: Mapping[str, object], message_id: str
312
+ ) -> Effect[Sequence[Row], SqlError]:
313
+ sql = self.sql
314
+
315
+ def unless_inserted(inserted: Sequence[object]) -> Effect[Sequence[Row], SqlError]:
316
+ return succeed([]) if inserted else self.select_by_message_id(message_id)
317
+
318
+ if sql.dialect == "pg":
319
+ return sql(
320
+ "INSERT INTO {} {} ON CONFLICT (message_id) DO NOTHING RETURNING id",
321
+ self.messages,
322
+ sql.insert(row),
323
+ ).flat_map(unless_inserted)
324
+ if sql.dialect == "mysql":
325
+
326
+ def affected(result: Any) -> Effect[Sequence[Row], SqlError]:
327
+ if affected_rows(result) > 0:
328
+ return succeed([])
329
+ return self.select_by_message_id(message_id)
330
+
331
+ return sql("INSERT IGNORE INTO {} {}", self.messages, sql.insert(row)).raw.flat_map(
332
+ affected
333
+ )
334
+ if sql.dialect == "mssql":
335
+ return sql(
336
+ "MERGE {} WITH (HOLDLOCK) AS target USING (SELECT {} as message_id) AS source "
337
+ "ON target.message_id = source.message_id WHEN NOT MATCHED THEN INSERT {} "
338
+ "OUTPUT inserted.id;",
339
+ self.messages,
340
+ message_id,
341
+ sql.insert(row),
342
+ ).flat_map(unless_inserted)
343
+ insert = sql("INSERT OR IGNORE INTO {} {}", self.messages, sql.insert(row))
344
+ return sql.with_transaction(
345
+ self.select_by_message_id(message_id).tap(lambda _: insert)
346
+ ).retry(times=3)
347
+
348
+ def address_filter(self, addresses: Sequence[EntityAddress], prefix: str) -> Fragment:
349
+ sql = self.sql
350
+ return sql.or_(
351
+ [
352
+ sql.and_(
353
+ [
354
+ sql(f"{prefix}shard_id = {{}}", shard), # pyright: ignore[reportArgumentType]
355
+ sql(f"{prefix}entity_type = {{}}", entity_type), # pyright: ignore[reportArgumentType]
356
+ sql.in_(f"{prefix}entity_id", ids),
357
+ ]
358
+ )
359
+ for shard, entity_type, ids in _group_addresses(addresses)
360
+ ]
361
+ )
362
+
363
+ def unprocessed_rows(
364
+ self,
365
+ shard_ids: Sequence[str],
366
+ now: int,
367
+ limit: int | None,
368
+ addresses: Sequence[EntityAddress] | None,
369
+ ) -> Effect[Sequence[Row], SqlError]:
370
+ sql = self.sql
371
+ address_filter = (
372
+ sql.literal("")
373
+ if addresses is None
374
+ else sql("AND ({})", self.address_filter(addresses, "m."))
375
+ )
376
+ if limit is None:
377
+ limit_fragment: Fragment = sql.literal("")
378
+ elif sql.dialect == "mssql":
379
+ limit_fragment = sql.unsafe(f"OFFSET 0 ROWS FETCH NEXT {int(limit)} ROWS ONLY")
380
+ else:
381
+ limit_fragment = sql.unsafe(f"LIMIT {int(limit)}")
382
+ deliverable = sql.unsafe(str(now))
383
+ if sql.dialect == "pg":
384
+ return sql(
385
+ "WITH messages AS ("
386
+ "UPDATE {} m SET last_read = {} FROM ("
387
+ "SELECT m.* FROM {} m WHERE m.shard_id IN ({}) {} "
388
+ "AND NOT EXISTS (SELECT 1 FROM {} WHERE request_id = m.request_id "
389
+ "AND (kind = {} OR acked = {})) "
390
+ "AND m.processed = {} "
391
+ "AND (m.last_read IS NULL OR m.last_read < {}) "
392
+ "AND (m.deliver_at IS NULL OR m.deliver_at <= {}) "
393
+ "ORDER BY m.rowid ASC {} FOR UPDATE"
394
+ ") AS ids LEFT JOIN {} r ON r.id = ids.last_reply_id "
395
+ "WHERE m.id = ids.id "
396
+ "RETURNING ids.*, r.id as reply_reply_id, r.kind as reply_kind, "
397
+ "r.payload as reply_payload, r.sequence as reply_sequence"
398
+ ") SELECT * FROM messages ORDER BY rowid ASC",
399
+ self.messages,
400
+ self.now,
401
+ self.messages,
402
+ self.shard_list(shard_ids),
403
+ address_filter,
404
+ self.replies,
405
+ self.kind_with_exit,
406
+ self.false,
407
+ self.false,
408
+ self.ten_minutes_ago,
409
+ deliverable,
410
+ limit_fragment,
411
+ self.replies,
412
+ )
413
+ select = sql(
414
+ "SELECT {} FROM {} m {} LEFT JOIN {} r ON r.id = m.last_reply_id "
415
+ "WHERE m.shard_id IN ({}) {} "
416
+ "AND NOT EXISTS (SELECT 1 FROM {} WHERE request_id = m.request_id "
417
+ "AND (kind = {} OR acked = {})) "
418
+ "AND processed = {} "
419
+ "AND (m.last_read IS NULL OR m.last_read < {}) "
420
+ "AND (m.deliver_at IS NULL OR m.deliver_at <= {}) "
421
+ "ORDER BY m.rowid ASC {} {}",
422
+ self.message_join_columns,
423
+ self.messages,
424
+ self.update_lock,
425
+ self.replies,
426
+ self.shard_list(shard_ids),
427
+ address_filter,
428
+ self.replies,
429
+ self.kind_with_exit,
430
+ self.false,
431
+ self.false,
432
+ self.ten_minutes_ago,
433
+ deliverable,
434
+ limit_fragment,
435
+ self.for_update,
436
+ ).unprepared
437
+
438
+ def claim(rows: Sequence[Row]) -> Effect[object, SqlError]:
439
+ if not rows:
440
+ return void
441
+ ids = sql.unsafe(",".join(str(row["id"]) for row in rows))
442
+ return sql(
443
+ "UPDATE {} SET last_read = {} WHERE id IN ({})", self.messages, self.now, ids
444
+ ).unprepared
445
+
446
+ return sql.with_transaction(select.tap(claim))
447
+
448
+ def reset_unprocessed(self, filter: Fragment) -> Effect[object, SqlError]:
449
+ sql = self.sql
450
+ if sql.dialect == "pg":
451
+ # the claim query's lock order, so resets cannot deadlock with it
452
+ return sql(
453
+ "WITH messages AS (SELECT id FROM {} WHERE processed = {} AND {} "
454
+ "ORDER BY rowid ASC FOR UPDATE) "
455
+ "UPDATE {} SET last_read = NULL WHERE id IN (SELECT id FROM messages)",
456
+ self.messages,
457
+ self.false,
458
+ filter,
459
+ self.messages,
460
+ )
461
+ return sql(
462
+ "UPDATE {} SET last_read = NULL WHERE processed = {} AND {}",
463
+ self.messages,
464
+ self.false,
465
+ filter,
466
+ )
467
+
468
+ # --- EncodedStorage ---------------------------------------------------------------
469
+
470
+ def save_envelope(
471
+ self, envelope: EnvelopeEncoded, primary_key: str | None, deliver_at: int | None, /
472
+ ) -> Effect[SaveResultEncoded, PersistenceError]:
473
+ sql = self.sql
474
+
475
+ def insert() -> Effect[Sequence[Row], SqlError]:
476
+ if primary_key is not None:
477
+ message_id = message_id_for_primary_key(primary_key)
478
+ row = self.envelope_row(envelope, message_id, deliver_at)
479
+ if not self.may_have_legacy_row(primary_key):
480
+ return self.insert_envelope(row, message_id)
481
+ return self.select_by_message_id(primary_key).flat_map(
482
+ lambda rows: succeed(rows) if rows else self.insert_envelope(row, message_id)
483
+ )
484
+ row = self.envelope_row(envelope, None, deliver_at)
485
+ plain = sql("INSERT INTO {} {}", self.messages, sql.insert(row)).unprepared.as_(
486
+ list[Row]()
487
+ )
488
+ if envelope["_tag"] != "AckChunk":
489
+ return plain
490
+ return sql.with_transaction(
491
+ sql(
492
+ "UPDATE {} SET acked = {} WHERE id = {}",
493
+ self.replies,
494
+ self.true,
495
+ envelope["replyId"],
496
+ )
497
+ .flat_map(
498
+ lambda _: sql(
499
+ "UPDATE {} SET processed = {} WHERE processed = {} AND request_id = {} "
500
+ "AND kind = {}",
501
+ self.messages,
502
+ self.true,
503
+ self.false,
504
+ envelope["requestId"],
505
+ self.kind_ack_chunk,
506
+ )
507
+ )
508
+ .flat_map(lambda _: plain)
509
+ )
510
+
511
+ def result(rows: Sequence[Row]) -> SaveResultEncoded:
512
+ if not rows:
513
+ return SAVE_SUCCESS
514
+ row = rows[0]
515
+ last: Encoded | None = None
516
+ if row["reply_id"]:
517
+ kind = row["reply_kind"]
518
+ if kind is not None and int(str(kind)) == _WITH_EXIT:
519
+ last = {
520
+ "_tag": "WithExit",
521
+ "id": _str(row["reply_id"]),
522
+ "requestId": _str(row["id"]),
523
+ "exit": _parse(str(row["reply_payload"])),
524
+ }
525
+ else:
526
+ last = {
527
+ "_tag": "Chunk",
528
+ "id": _str(row["reply_id"]),
529
+ "requestId": _str(row["id"]),
530
+ "sequence": int(str(row["reply_sequence"])),
531
+ "values": _parse(str(row["reply_payload"])),
532
+ }
533
+ return DuplicateEncoded(
534
+ original_id=Snowflake(int(str(row["id"]))), last_received_reply=last
535
+ )
536
+
537
+ return _refail(suspend(insert).map(result))
538
+
539
+ def save_reply(self, reply: Encoded, /) -> Effect[None, PersistenceError]:
540
+ sql = self.sql
541
+
542
+ def run() -> Effect[object, SqlError]:
543
+ if reply["_tag"] == "WithExit":
544
+ kind: object = _WITH_EXIT
545
+ payload = _json_text.stringify(reply["exit"])
546
+ sequence: object = None
547
+ update = sql(
548
+ "UPDATE {} SET processed = {}, last_reply_id = {} WHERE request_id = {}",
549
+ self.messages,
550
+ self.true,
551
+ reply["id"],
552
+ reply["requestId"],
553
+ )
554
+ else:
555
+ kind = None
556
+ payload = _json_text.stringify(list(reply["values"]))
557
+ sequence = reply["sequence"]
558
+ update = sql(
559
+ "UPDATE {} SET last_reply_id = {} WHERE id = {}",
560
+ self.messages,
561
+ reply["id"],
562
+ reply["requestId"],
563
+ )
564
+ row = {
565
+ "id": reply["id"],
566
+ "kind": kind,
567
+ "request_id": reply["requestId"],
568
+ "payload": payload,
569
+ "sequence": sequence,
570
+ }
571
+ insert = sql("INSERT INTO {} {}", self.replies, sql.insert(row))
572
+ return sql.with_transaction(update.unprepared.flat_map(lambda _: insert))
573
+
574
+ return _refail(suspend(run).as_void())
575
+
576
+ def clear_replies(
577
+ self, request_id: Snowflake, /, *, expected_reply_id: Snowflake | None = None
578
+ ) -> Effect[None, PersistenceError]:
579
+ sql = self.sql
580
+ id = str(request_id)
581
+
582
+ def body() -> Generator[Effect[Any, Any, Any], Any]:
583
+ if expected_reply_id is None:
584
+ yield from sql(
585
+ "DELETE FROM {} WHERE request_id = {} AND kind = 0", self.replies, id
586
+ )
587
+ yield from sql(
588
+ "DELETE FROM {} WHERE request_id = {} AND kind = {}",
589
+ self.messages,
590
+ id,
591
+ self.kind_interrupt,
592
+ )
593
+ yield from sql(
594
+ "UPDATE {} SET processed = {}, last_reply_id = NULL, last_read = NULL "
595
+ "WHERE request_id = {}",
596
+ self.messages,
597
+ self.false,
598
+ id,
599
+ )
600
+ return
601
+ expected = str(expected_reply_id)
602
+ # checked here, at the storage boundary, not at an earlier read
603
+ yield from sql(
604
+ "DELETE FROM {} WHERE request_id = {} AND kind = 0 "
605
+ "AND EXISTS (SELECT 1 FROM {} WHERE id = {} AND last_reply_id = {})",
606
+ self.replies,
607
+ id,
608
+ self.messages,
609
+ id,
610
+ expected,
611
+ )
612
+ yield from sql(
613
+ "UPDATE {} SET processed = {}, last_reply_id = NULL, last_read = NULL "
614
+ "WHERE request_id = {} AND last_reply_id = {}",
615
+ self.messages,
616
+ self.false,
617
+ id,
618
+ expected,
619
+ )
620
+
621
+ return _refail(sql.with_transaction(gen(body)))
622
+
623
+ def request_id_for_primary_key(
624
+ self, primary_key: str, /
625
+ ) -> Effect[Snowflake | None, PersistenceError]:
626
+ sql = self.sql
627
+
628
+ def select(key: str) -> Effect[Sequence[Row], SqlError]:
629
+ return sql(
630
+ "SELECT {} FROM {} WHERE message_id = {}", self.id_column, self.messages, key
631
+ )
632
+
633
+ def legacy(rows: Sequence[Row]) -> Effect[Sequence[Row], SqlError]:
634
+ if not rows and self.may_have_legacy_row(primary_key):
635
+ return select(primary_key)
636
+ return succeed(rows)
637
+
638
+ return _refail(
639
+ select(message_id_for_primary_key(primary_key))
640
+ .flat_map(legacy)
641
+ .map(lambda rows: Snowflake(int(str(rows[0]["id"]))) if rows else None)
642
+ )
643
+
644
+ def replies_for(self, request_ids: Sequence[str], /) -> Effect[list[Encoded], PersistenceError]:
645
+ # exits, and chunks not acknowledged yet
646
+ sql = self.sql
647
+ return _refail(
648
+ sql(
649
+ "SELECT {} FROM {} WHERE request_id IN ({}) "
650
+ "AND (kind = {} OR (kind IS NULL AND acked = {})) ORDER BY rowid ASC",
651
+ self.reply_columns,
652
+ self.replies,
653
+ sql.unsafe(",".join(str(int(i)) for i in request_ids)),
654
+ self.kind_with_exit,
655
+ self.false,
656
+ ).unprepared.map(lambda rows: [_reply_from_row(r) for r in rows])
657
+ )
658
+
659
+ def replies_for_unfiltered(
660
+ self, request_ids: Sequence[str], /
661
+ ) -> Effect[list[Encoded], PersistenceError]:
662
+ sql = self.sql
663
+ return _refail(
664
+ sql(
665
+ "SELECT {} FROM {} WHERE request_id IN ({}) ORDER BY rowid ASC",
666
+ self.reply_columns,
667
+ self.replies,
668
+ sql.unsafe(",".join(str(int(i)) for i in request_ids)),
669
+ ).unprepared.map(lambda rows: [_reply_from_row(r) for r in rows])
670
+ )
671
+
672
+ def unprocessed_messages(
673
+ self,
674
+ shard_ids: Sequence[str],
675
+ now: int,
676
+ /,
677
+ *,
678
+ limit: int | None = None,
679
+ addresses: Sequence[EntityAddress] | None = None,
680
+ ) -> Effect[list[EncodedMessage], PersistenceError]:
681
+ if addresses is not None and len(addresses) == 0:
682
+ return succeed([])
683
+ return _refail(
684
+ self.unprocessed_rows(shard_ids, now, limit, addresses).map(
685
+ lambda rows: [_message_from_row(r) for r in rows]
686
+ )
687
+ )
688
+
689
+ def unprocessed_messages_by_id(
690
+ self, message_ids: Sequence[Snowflake], now: int, /
691
+ ) -> Effect[list[EncodedMessage], PersistenceError]:
692
+ sql = self.sql
693
+ return _refail(
694
+ sql(
695
+ "SELECT {} FROM {} m LEFT JOIN {} r ON r.id = m.last_reply_id "
696
+ "WHERE m.id IN ({}) "
697
+ "AND NOT EXISTS (SELECT 1 FROM {} WHERE request_id = m.request_id "
698
+ "AND (kind = {} OR acked = {})) "
699
+ "AND m.processed = {} "
700
+ "AND (m.deliver_at IS NULL OR m.deliver_at <= {}) "
701
+ "ORDER BY m.rowid ASC",
702
+ self.message_join_columns,
703
+ self.messages,
704
+ self.replies,
705
+ sql.unsafe(",".join(str(int(i)) for i in message_ids)),
706
+ self.replies,
707
+ self.kind_with_exit,
708
+ self.false,
709
+ self.false,
710
+ sql.unsafe(str(now)),
711
+ ).unprepared.map(lambda rows: [_message_from_row(r) for r in rows])
712
+ )
713
+
714
+ def reset_requests(self, request_ids: Sequence[Snowflake], /) -> Effect[None, PersistenceError]:
715
+ if not request_ids:
716
+ return void
717
+ sql = self.sql
718
+ return _refail(
719
+ sql(
720
+ "UPDATE {} SET last_read = NULL WHERE processed = {} AND {}",
721
+ self.messages,
722
+ self.false,
723
+ sql.in_("id", [str(i) for i in request_ids]),
724
+ ).as_void()
725
+ )
726
+
727
+ def reset_addresses(
728
+ self, addresses: Sequence[EntityAddress], /
729
+ ) -> Effect[None, PersistenceError]:
730
+ if not addresses:
731
+ return void
732
+ filter = self.sql("({})", self.address_filter(addresses, ""))
733
+ return _refail(self.reset_unprocessed(filter).as_void())
734
+
735
+ def clear_address(self, address: EntityAddress, /) -> Effect[None, PersistenceError]:
736
+ sql = self.sql
737
+ replies = sql(
738
+ "DELETE FROM {} WHERE request_id IN (SELECT id FROM {} WHERE entity_type = {} "
739
+ "AND entity_id = {})",
740
+ self.replies,
741
+ self.messages,
742
+ address.entity_type,
743
+ address.entity_id,
744
+ )
745
+ messages = sql(
746
+ "DELETE FROM {} WHERE entity_type = {} AND entity_id = {}",
747
+ self.messages,
748
+ address.entity_type,
749
+ address.entity_id,
750
+ )
751
+ return _refail(sql.with_transaction(replies.flat_map(lambda _: messages)).as_void())
752
+
753
+ def reset_shards(self, shard_ids: Sequence[str], /) -> Effect[None, PersistenceError]:
754
+ filter = self.sql("shard_id IN ({})", self.shard_list(shard_ids))
755
+ return _refail(self.reset_unprocessed(filter).as_void())
756
+
757
+ def with_transaction[A, E, R](self, effect: Effect[A, E, R], /) -> Effect[A, E, R]:
758
+ require_effect(effect, "effect")
759
+
760
+ def defect(cause: Cause[Any]) -> Effect[A, E, R]:
761
+ found = cause.find_recoverable_error()
762
+ if isinstance(found, Fail) and is_sql_error(found.error):
763
+ return die(found.error)
764
+ return fail_cause(found if not isinstance(found, Fail) else cause)
765
+
766
+ return self.sql.with_transaction(effect).catch_cause(defect)
767
+
768
+
769
+ def _refail[A](effect: Effect[A, Any]) -> Effect[A, PersistenceError]:
770
+ return PersistenceError.refail(effect).with_tracer_enabled(False)
771
+
772
+
773
+ def make_encoded_storage(*, prefix: str = "cluster") -> Effect[EncodedStorage, Never, SqlClient]:
774
+ """The driver on the tables of ``prefix`` (already migrated)."""
775
+ return SqlClient().map(lambda sql: _Storage(sql.without_transforms(), prefix))
776
+
777
+
778
+ def make(
779
+ *, prefix: str = "cluster"
780
+ ) -> Effect[MessageStorageService, Never, SqlClient | snowflake_.Generator]:
781
+ """Run the migrations, then a message storage on the tables."""
782
+ return (
783
+ run_migrations(prefix=prefix)
784
+ .flat_map(lambda _: make_encoded_storage(prefix=prefix))
785
+ .flat_map(make_encoded)
786
+ )
787
+
788
+
789
+ def _create_messages(sql: SqlClientService, table: str) -> Effect[object, SqlError]:
790
+ messages = sql.identifier(table)
791
+ return sql.on_dialect_or_else(
792
+ mssql=lambda: sql(
793
+ "IF OBJECT_ID(N'{}', N'U') IS NULL CREATE TABLE {} ("
794
+ "id BIGINT PRIMARY KEY, rowid BIGINT IDENTITY(1,1), message_id VARCHAR(255), "
795
+ "shard_id VARCHAR(50) NOT NULL, entity_type VARCHAR(150) NOT NULL, "
796
+ "entity_id VARCHAR(255) NOT NULL, kind INT NOT NULL, tag VARCHAR(50), "
797
+ "payload NVARCHAR(MAX), headers NVARCHAR(MAX), trace_id VARCHAR(32), "
798
+ "span_id VARCHAR(16), sampled BIT, processed BIT NOT NULL DEFAULT 0, "
799
+ "request_id BIGINT NOT NULL, reply_id BIGINT, last_reply_id BIGINT, "
800
+ "last_read DATETIME, deliver_at BIGINT, "
801
+ "INDEX {} UNIQUE (message_id) WHERE message_id IS NOT NULL)",
802
+ messages,
803
+ messages,
804
+ sql.identifier(f"{table}_message_id_idx"),
805
+ ),
806
+ mysql=lambda: sql(
807
+ "CREATE TABLE IF NOT EXISTS {} ("
808
+ "id BIGINT NOT NULL, rowid BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, "
809
+ "message_id VARCHAR(255), shard_id VARCHAR(50) NOT NULL, "
810
+ "entity_type VARCHAR(150) NOT NULL, entity_id VARCHAR(255) NOT NULL, "
811
+ "kind INT NOT NULL, tag VARCHAR(50), payload TEXT, headers TEXT, "
812
+ "trace_id VARCHAR(32), span_id VARCHAR(16), sampled BOOLEAN, "
813
+ "processed BOOLEAN NOT NULL DEFAULT FALSE, request_id BIGINT NOT NULL, "
814
+ "reply_id BIGINT, last_reply_id BIGINT, last_read DATETIME, deliver_at BIGINT, "
815
+ "UNIQUE (id), UNIQUE (message_id))",
816
+ messages,
817
+ ),
818
+ pg=lambda: sql(
819
+ "CREATE TABLE IF NOT EXISTS {} ("
820
+ "id BIGINT PRIMARY KEY, rowid BIGSERIAL, message_id VARCHAR(255), "
821
+ "shard_id VARCHAR(50) NOT NULL, entity_type VARCHAR(150) NOT NULL, "
822
+ "entity_id VARCHAR(255) NOT NULL, kind INT NOT NULL, tag VARCHAR(50), "
823
+ "payload TEXT, headers TEXT, trace_id VARCHAR(32), span_id VARCHAR(16), "
824
+ "sampled BOOLEAN, processed BOOLEAN NOT NULL DEFAULT FALSE, "
825
+ "request_id BIGINT NOT NULL, reply_id BIGINT, last_reply_id BIGINT, "
826
+ "last_read TIMESTAMP, deliver_at BIGINT, UNIQUE (message_id))",
827
+ messages,
828
+ ),
829
+ or_else=lambda: sql(
830
+ "CREATE TABLE IF NOT EXISTS {} ("
831
+ "id INTEGER PRIMARY KEY, message_id TEXT, shard_id TEXT NOT NULL, "
832
+ "entity_type TEXT NOT NULL, entity_id TEXT NOT NULL, kind INTEGER NOT NULL, "
833
+ "tag TEXT, payload TEXT, headers TEXT, trace_id TEXT, span_id TEXT, "
834
+ "sampled BOOLEAN, processed BOOLEAN NOT NULL DEFAULT FALSE, "
835
+ "request_id INTEGER NOT NULL, reply_id INTEGER, last_reply_id INTEGER, "
836
+ "last_read TEXT, deliver_at INTEGER, UNIQUE (message_id))",
837
+ messages,
838
+ ),
839
+ )
840
+
841
+
842
+ def _create_replies(sql: SqlClientService, table: str) -> Effect[object, SqlError]:
843
+ replies = sql.identifier(table)
844
+ return sql.on_dialect_or_else(
845
+ mssql=lambda: sql(
846
+ "IF OBJECT_ID(N'{}', N'U') IS NULL CREATE TABLE {} ("
847
+ "id BIGINT PRIMARY KEY, rowid BIGINT IDENTITY(1,1), kind INT, "
848
+ "request_id BIGINT NOT NULL, payload NVARCHAR(MAX) NOT NULL, sequence INT, "
849
+ "acked BIT NOT NULL DEFAULT 0, "
850
+ "INDEX {} UNIQUE (request_id, kind) WHERE kind IS NOT NULL, "
851
+ "INDEX {} UNIQUE (request_id, sequence) WHERE sequence IS NOT NULL)",
852
+ replies,
853
+ replies,
854
+ sql.identifier(f"{table}_one_exit"),
855
+ sql.identifier(f"{table}_sequence"),
856
+ ),
857
+ mysql=lambda: sql(
858
+ "CREATE TABLE IF NOT EXISTS {} ("
859
+ "id BIGINT NOT NULL, rowid BIGINT AUTO_INCREMENT PRIMARY KEY, kind INT, "
860
+ "request_id BIGINT NOT NULL, payload TEXT NOT NULL, sequence INT, "
861
+ "acked BOOLEAN NOT NULL DEFAULT FALSE, UNIQUE (id), UNIQUE (request_id, kind), "
862
+ "UNIQUE (request_id, sequence))",
863
+ replies,
864
+ ),
865
+ pg=lambda: sql(
866
+ "CREATE TABLE IF NOT EXISTS {} ("
867
+ "id BIGINT PRIMARY KEY, rowid BIGSERIAL, kind INT, request_id BIGINT NOT NULL, "
868
+ "payload TEXT NOT NULL, sequence INT, acked BOOLEAN NOT NULL DEFAULT FALSE, "
869
+ "UNIQUE (request_id, kind), UNIQUE (request_id, sequence))",
870
+ replies,
871
+ ),
872
+ or_else=lambda: sql(
873
+ "CREATE TABLE IF NOT EXISTS {} ("
874
+ "id INTEGER PRIMARY KEY, kind INTEGER, request_id INTEGER NOT NULL, "
875
+ "payload TEXT NOT NULL, sequence INTEGER, acked BOOLEAN NOT NULL DEFAULT FALSE, "
876
+ "UNIQUE (request_id, kind), UNIQUE (request_id, sequence))",
877
+ replies,
878
+ ),
879
+ )
880
+
881
+
882
+ def _index(sql: SqlClientService, name: str, table: str, columns: str) -> Effect[object, SqlError]:
883
+ index = sql.identifier(name)
884
+ target = sql.identifier(table)
885
+ cols = sql.unsafe(columns)
886
+ return sql.on_dialect_or_else(
887
+ mssql=lambda: sql(
888
+ "IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = {}) CREATE INDEX {} ON {} ({})",
889
+ name,
890
+ index,
891
+ target,
892
+ cols,
893
+ ),
894
+ # MySQL has no IF NOT EXISTS for indexes: an existing one fails
895
+ mysql=lambda: sql("CREATE INDEX {} ON {} ({})", index, target, cols).unprepared.ignore(
896
+ log=False
897
+ ),
898
+ or_else=lambda: sql("CREATE INDEX IF NOT EXISTS {} ON {} ({})", index, target, cols),
899
+ )
900
+
901
+
902
+ def migrations(*, prefix: str = "cluster") -> migrator.Loader:
903
+ """The message tables' migrations. PostgreSQL DDL is serialized by the
904
+ migrator; errors propagate (a retry in an aborted transaction cannot
905
+ succeed)."""
906
+ messages_table = f"{prefix}_messages"
907
+ replies_table = f"{prefix}_replies"
908
+
909
+ def create_tables() -> Generator[Effect[Any, Any, Any], Any]:
910
+ sql = (yield from SqlClient()).without_transforms()
911
+ yield from _create_messages(sql, messages_table)
912
+ yield from _index(
913
+ sql,
914
+ f"{messages_table}_shard_idx",
915
+ messages_table,
916
+ "shard_id, processed, last_read, deliver_at",
917
+ )
918
+ yield from _index(sql, f"{messages_table}_request_id_idx", messages_table, "request_id")
919
+ yield from _create_replies(sql, replies_table)
920
+ yield from _index(
921
+ sql, f"{replies_table}_request_lookup_idx", replies_table, "request_id, kind, acked"
922
+ )
923
+
924
+ def entity_type_size() -> Generator[Effect[Any, Any, Any], Any]:
925
+ sql = (yield from SqlClient()).without_transforms()
926
+ messages = sql.identifier(messages_table)
927
+ yield from sql.on_dialect_or_else(
928
+ mssql=lambda: sql(
929
+ "ALTER TABLE {} ALTER COLUMN entity_type VARCHAR(150) NOT NULL;", messages
930
+ ),
931
+ mysql=lambda: sql(
932
+ "ALTER TABLE {} MODIFY entity_type VARCHAR(150) NOT NULL;", messages
933
+ ).unprepared.ignore(log=False),
934
+ pg=lambda: sql("ALTER TABLE {} ALTER COLUMN entity_type TYPE VARCHAR(150);", messages),
935
+ or_else=lambda: void,
936
+ )
937
+
938
+ def pg_rowid_index() -> Generator[Effect[Any, Any, Any], Any]:
939
+ sql = (yield from SqlClient()).without_transforms()
940
+ yield from sql.on_dialect_or_else(
941
+ pg=lambda: _index(sql, f"{messages_table}_rowid_idx", messages_table, "rowid"),
942
+ or_else=lambda: void,
943
+ )
944
+
945
+ return migrator.from_record(
946
+ {
947
+ "0001_create_tables": gen(create_tables),
948
+ "0002_entity_type_size": gen(entity_type_size),
949
+ "0003_pg_messages_rowid_index": gen(pg_rowid_index),
950
+ }
951
+ )
952
+
953
+
954
+ def run_migrations(*, prefix: str = "cluster") -> Effect[None, Never, SqlClient]:
955
+ """Run the message migrations; a failure is a defect."""
956
+ return (
957
+ migrator.make()(loader=migrations(prefix=prefix), table=f"{prefix}_migrations")
958
+ .as_void()
959
+ .or_die()
960
+ )
961
+
962
+
963
+ def layer_migrations(*, prefix: str = "cluster") -> layer_.Layer[Never, Never, SqlClient]:
964
+ return layer_.effect_discard(run_migrations(prefix=prefix))
965
+
966
+
967
+ def layer_storage(
968
+ *, prefix: str = "cluster"
969
+ ) -> layer_.Layer[MessageStorage, Never, SqlClient | ShardingConfig]:
970
+ """Message storage on migrated tables (with its own snowflake
971
+ generator)."""
972
+ return layer_.effect(
973
+ MessageStorage, make_encoded_storage(prefix=prefix).flat_map(make_encoded)
974
+ ).pipe(layer_.provide(snowflake_.layer_generator))
975
+
976
+
977
+ def layer_with(
978
+ *, prefix: str = "cluster"
979
+ ) -> layer_.Layer[MessageStorage, Never, SqlClient | ShardingConfig]:
980
+ """``layer_storage`` after the migrations."""
981
+ return layer_storage(prefix=prefix).pipe(layer_.provide(layer_migrations(prefix=prefix)))
982
+
983
+
984
+ layer: layer_.Layer[MessageStorage, Never, SqlClient | ShardingConfig] = layer_with()
985
+
986
+ __all__ = [
987
+ "layer",
988
+ "layer_migrations",
989
+ "layer_storage",
990
+ "layer_with",
991
+ "make",
992
+ "make_encoded_storage",
993
+ "message_id_for_primary_key",
994
+ "migrations",
995
+ "run_migrations",
996
+ ]
997
+
998
+
999
+ def _parse(text: str) -> Any:
1000
+ """A stored JSON column (written by ``_json_text.stringify``)."""
1001
+ return _json_text.parse(text)