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.
- pyfect/__init__.py +729 -0
- pyfect/_all.py +242 -0
- pyfect/_background.py +28 -0
- pyfect/_binary_codecs.py +124 -0
- pyfect/_cached.py +165 -0
- pyfect/_catch.py +128 -0
- pyfect/_catch_reason.py +574 -0
- pyfect/_concurrency.py +291 -0
- pyfect/_context_effects.py +163 -0
- pyfect/_context_managers.py +76 -0
- pyfect/_core.py +368 -0
- pyfect/_digits.py +71 -0
- pyfect/_effect_args.py +46 -0
- pyfect/_effect_base.py +71 -0
- pyfect/_effect_concurrency.py +133 -0
- pyfect/_effect_eager.py +103 -0
- pyfect/_effect_errors.py +344 -0
- pyfect/_effect_folding.py +110 -0
- pyfect/_effect_hash.py +140 -0
- pyfect/_effect_logging.py +57 -0
- pyfect/_effect_metrics.py +102 -0
- pyfect/_effect_resources.py +179 -0
- pyfect/_effect_services.py +25 -0
- pyfect/_effect_timing.py +199 -0
- pyfect/_effect_tracing.py +99 -0
- pyfect/_equal.py +195 -0
- pyfect/_extras.py +43 -0
- pyfect/_fiber.py +969 -0
- pyfect/_fiber_all.py +134 -0
- pyfect/_fiber_collections.py +106 -0
- pyfect/_fn.py +706 -0
- pyfect/_hamt.py +174 -0
- pyfect/_interrupt_reason.py +33 -0
- pyfect/_isaac.py +149 -0
- pyfect/_iterables.py +231 -0
- pyfect/_js.py +263 -0
- pyfect/_js_date.py +87 -0
- pyfect/_json_text.py +286 -0
- pyfect/_json_value.py +9 -0
- pyfect/_layer_core.py +403 -0
- pyfect/_layer_for_life.py +33 -0
- pyfect/_layer_tracing.py +70 -0
- pyfect/_locked_ref.py +147 -0
- pyfect/_loop_thread.py +152 -0
- pyfect/_metric_ref.py +29 -0
- pyfect/_one_or_many.py +14 -0
- pyfect/_os_wait.py +118 -0
- pyfect/_path_posix.py +129 -0
- pyfect/_primitives.py +434 -0
- pyfect/_public.py +36 -0
- pyfect/_reasons.py +64 -0
- pyfect/_released.py +67 -0
- pyfect/_root.py +87 -0
- pyfect/_round_robin.py +73 -0
- pyfect/_some.py +17 -0
- pyfect/_stack.py +52 -0
- pyfect/_stack_frame.py +114 -0
- pyfect/_string_hash.py +35 -0
- pyfect/_subscriber_queue.py +47 -0
- pyfect/_tags.py +14 -0
- pyfect/_tracer_ref.py +21 -0
- pyfect/_tx_ops.py +77 -0
- pyfect/_unsubscripted.py +23 -0
- pyfect/_url.py +54 -0
- pyfect/_waiters.py +127 -0
- pyfect/_wake_tracking.py +98 -0
- pyfect/_wire_codec.py +123 -0
- pyfect/ai/__init__.py +73 -0
- pyfect/ai/_approvals.py +249 -0
- pyfect/ai/_decision_answers.py +185 -0
- pyfect/ai/_generated_client.py +298 -0
- pyfect/ai/_generated_support.py +84 -0
- pyfect/ai/_mcp/__init__.py +1 -0
- pyfect/ai/_mcp/batch.py +68 -0
- pyfect/ai/_mcp/client_transport.py +412 -0
- pyfect/ai/_mcp/client_wire.py +284 -0
- pyfect/ai/_mcp/core.py +490 -0
- pyfect/ai/_mcp/dispatch.py +1048 -0
- pyfect/ai/_mcp/http.py +462 -0
- pyfect/ai/_mcp/http_admission.py +269 -0
- pyfect/ai/_mcp/http_status.py +27 -0
- pyfect/ai/_mcp/input_wire.py +226 -0
- pyfect/ai/_mcp/log_levels.py +55 -0
- pyfect/ai/_mcp/param_headers.py +63 -0
- pyfect/ai/_mcp/projection.py +248 -0
- pyfect/ai/_mcp/register.py +683 -0
- pyfect/ai/_mcp/reverse_policy.py +155 -0
- pyfect/ai/_mcp/routing_header.py +24 -0
- pyfect/ai/_mcp/sessions.py +114 -0
- pyfect/ai/_mcp/stdio.py +110 -0
- pyfect/ai/_mcp/subscriptions.py +134 -0
- pyfect/ai/_mcp/tool_schema.py +35 -0
- pyfect/ai/_noul_answers.py +64 -0
- pyfect/ai/_openai_all_of.py +137 -0
- pyfect/ai/_openai_errors.py +233 -0
- pyfect/ai/_provider.py +439 -0
- pyfect/ai/_provider_tools.py +129 -0
- pyfect/ai/_stream_resolution.py +231 -0
- pyfect/ai/_structured_output_checks.py +138 -0
- pyfect/ai/_structured_output_codec.py +229 -0
- pyfect/ai/_structured_output_json.py +109 -0
- pyfect/ai/_tool_call_ids.py +80 -0
- pyfect/ai/ai_error.py +785 -0
- pyfect/ai/anthropic/__init__.py +28 -0
- pyfect/ai/anthropic/_errors.py +164 -0
- pyfect/ai/anthropic/_prompt.py +375 -0
- pyfect/ai/anthropic/_response.py +443 -0
- pyfect/ai/anthropic/_schema.py +206 -0
- pyfect/ai/anthropic/_stream.py +377 -0
- pyfect/ai/anthropic/_tools.py +248 -0
- pyfect/ai/anthropic/client.py +289 -0
- pyfect/ai/anthropic/config.py +32 -0
- pyfect/ai/anthropic/error.py +27 -0
- pyfect/ai/anthropic/language_model.py +366 -0
- pyfect/ai/anthropic/telemetry.py +51 -0
- pyfect/ai/anthropic/tool.py +570 -0
- pyfect/ai/anthropic_structured_output.py +138 -0
- pyfect/ai/chat.py +548 -0
- pyfect/ai/cloudflare/__init__.py +15 -0
- pyfect/ai/cloudflare/_errors.py +108 -0
- pyfect/ai/cloudflare/client.py +174 -0
- pyfect/ai/cloudflare/decision_model.py +117 -0
- pyfect/ai/cloudflare/schema.py +144 -0
- pyfect/ai/decision.py +290 -0
- pyfect/ai/decision_model.py +283 -0
- pyfect/ai/embedding_model.py +177 -0
- pyfect/ai/id_generator.py +79 -0
- pyfect/ai/language_model.py +1077 -0
- pyfect/ai/mcp_client.py +682 -0
- pyfect/ai/mcp_client_error.py +223 -0
- pyfect/ai/mcp_protocol.py +205 -0
- pyfect/ai/mcp_schema.py +1300 -0
- pyfect/ai/mcp_server.py +404 -0
- pyfect/ai/model.py +56 -0
- pyfect/ai/openai/__init__.py +49 -0
- pyfect/ai/openai/_errors.py +89 -0
- pyfect/ai/openai/_prompt.py +527 -0
- pyfect/ai/openai/_requests.py +34 -0
- pyfect/ai/openai/_response.py +569 -0
- pyfect/ai/openai/_schema.py +769 -0
- pyfect/ai/openai/_stream.py +771 -0
- pyfect/ai/openai/_tools.py +128 -0
- pyfect/ai/openai/_web_socket.py +295 -0
- pyfect/ai/openai/client.py +296 -0
- pyfect/ai/openai/client_generated.py +123 -0
- pyfect/ai/openai/config.py +34 -0
- pyfect/ai/openai/decision_model.py +177 -0
- pyfect/ai/openai/embedding_model.py +116 -0
- pyfect/ai/openai/error.py +8 -0
- pyfect/ai/openai/generated/__init__.py +7213 -0
- pyfect/ai/openai/generated/_recursive.py +18 -0
- pyfect/ai/openai/generated/_schemas_0.py +1015 -0
- pyfect/ai/openai/generated/_schemas_1.py +1016 -0
- pyfect/ai/openai/generated/_schemas_2.py +1017 -0
- pyfect/ai/openai/generated/_schemas_3.py +615 -0
- pyfect/ai/openai/generated/client.py +5298 -0
- pyfect/ai/openai/generated/types.py +8123 -0
- pyfect/ai/openai/language_model.py +304 -0
- pyfect/ai/openai/telemetry.py +19 -0
- pyfect/ai/openai/tool.py +391 -0
- pyfect/ai/openai_compat/__init__.py +25 -0
- pyfect/ai/openai_compat/_prompt.py +191 -0
- pyfect/ai/openai_compat/_request.py +153 -0
- pyfect/ai/openai_compat/_response.py +407 -0
- pyfect/ai/openai_compat/_schema.py +97 -0
- pyfect/ai/openai_compat/client.py +234 -0
- pyfect/ai/openai_compat/config.py +32 -0
- pyfect/ai/openai_compat/embedding_model.py +139 -0
- pyfect/ai/openai_compat/error.py +7 -0
- pyfect/ai/openai_compat/language_model.py +245 -0
- pyfect/ai/openai_compat/telemetry.py +51 -0
- pyfect/ai/openai_structured_output.py +118 -0
- pyfect/ai/openrouter/__init__.py +36 -0
- pyfect/ai/openrouter/_errors.py +186 -0
- pyfect/ai/openrouter/_prompt.py +272 -0
- pyfect/ai/openrouter/_response.py +226 -0
- pyfect/ai/openrouter/_stream.py +320 -0
- pyfect/ai/openrouter/client.py +283 -0
- pyfect/ai/openrouter/config.py +36 -0
- pyfect/ai/openrouter/decision_model.py +161 -0
- pyfect/ai/openrouter/error.py +23 -0
- pyfect/ai/openrouter/generated/__init__.py +3029 -0
- pyfect/ai/openrouter/generated/_recursive.py +15 -0
- pyfect/ai/openrouter/generated/_schemas_0.py +1015 -0
- pyfect/ai/openrouter/generated/_schemas_1.py +522 -0
- pyfect/ai/openrouter/generated/client.py +1445 -0
- pyfect/ai/openrouter/generated/types.py +4769 -0
- pyfect/ai/openrouter/language_model.py +345 -0
- pyfect/ai/openrouter/schema.py +165 -0
- pyfect/ai/prompt.py +664 -0
- pyfect/ai/response.py +768 -0
- pyfect/ai/response_id_tracker.py +82 -0
- pyfect/ai/telemetry.py +126 -0
- pyfect/ai/tokenizer.py +61 -0
- pyfect/ai/tool.py +536 -0
- pyfect/ai/toolkit.py +490 -0
- pyfect/ai/typesafe/__init__.py +17 -0
- pyfect/ai/typesafe/_errors.py +120 -0
- pyfect/ai/typesafe/client.py +146 -0
- pyfect/ai/typesafe/config.py +34 -0
- pyfect/ai/typesafe/decision_model.py +90 -0
- pyfect/ai/typesafe/schema.py +140 -0
- pyfect/batching/__init__.py +58 -0
- pyfect/batching/_api.py +71 -0
- pyfect/batching/_engine.py +229 -0
- pyfect/batching/_from_effect.py +76 -0
- pyfect/batching/_persisted.py +98 -0
- pyfect/batching/_request.py +133 -0
- pyfect/batching/_resolver.py +477 -0
- pyfect/batching/_with_cache.py +90 -0
- pyfect/big_decimal.py +500 -0
- pyfect/brand.py +114 -0
- pyfect/byte_size.py +354 -0
- pyfect/cache/__init__.py +29 -0
- pyfect/cache/_cache.py +426 -0
- pyfect/cache/_expiry.py +69 -0
- pyfect/cache/_scoped_cache.py +546 -0
- pyfect/case.py +174 -0
- pyfect/cause.py +462 -0
- pyfect/channel/__init__.py +155 -0
- pyfect/channel/_base.py +92 -0
- pyfect/channel/_catch.py +158 -0
- pyfect/channel/_catch_reason.py +572 -0
- pyfect/channel/_channel.py +158 -0
- pyfect/channel/_combinators.py +220 -0
- pyfect/channel/_concurrency_methods.py +217 -0
- pyfect/channel/_concurrent_map.py +118 -0
- pyfect/channel/_constructors.py +349 -0
- pyfect/channel/_context.py +232 -0
- pyfect/channel/_error_methods.py +336 -0
- pyfect/channel/_errors.py +180 -0
- pyfect/channel/_filters.py +225 -0
- pyfect/channel/_lifecycle.py +71 -0
- pyfect/channel/_lifecycle_methods.py +206 -0
- pyfect/channel/_loop.py +100 -0
- pyfect/channel/_merge.py +246 -0
- pyfect/channel/_repeat.py +154 -0
- pyfect/channel/_run.py +149 -0
- pyfect/channel/_run_methods.py +341 -0
- pyfect/channel/_sinks.py +88 -0
- pyfect/channel/_sources.py +238 -0
- pyfect/channel/_text.py +176 -0
- pyfect/channel/_transform_methods.py +415 -0
- pyfect/channel/_upstream.py +67 -0
- pyfect/channel_schema.py +131 -0
- pyfect/cli/__init__.py +79 -0
- pyfect/cli/_ansi.py +82 -0
- pyfect/cli/_command.py +469 -0
- pyfect/cli/_config.py +164 -0
- pyfect/cli/_environment.py +10 -0
- pyfect/cli/_help.py +195 -0
- pyfect/cli/_lexer.py +81 -0
- pyfect/cli/_parser.py +465 -0
- pyfect/cli/_settings.py +85 -0
- pyfect/cli/_suggest.py +33 -0
- pyfect/cli/_width.py +134 -0
- pyfect/cli/_wizard.py +317 -0
- pyfect/cli/argument.py +212 -0
- pyfect/cli/cli_config.py +40 -0
- pyfect/cli/cli_error.py +242 -0
- pyfect/cli/cli_output.py +305 -0
- pyfect/cli/command.py +440 -0
- pyfect/cli/completions/__init__.py +50 -0
- pyfect/cli/completions/_completer.ps1 +190 -0
- pyfect/cli/completions/_descriptor.py +182 -0
- pyfect/cli/completions/bash.py +300 -0
- pyfect/cli/completions/fish.py +164 -0
- pyfect/cli/completions/powershell.py +156 -0
- pyfect/cli/completions/zsh.py +189 -0
- pyfect/cli/flag.py +246 -0
- pyfect/cli/global_flag.py +99 -0
- pyfect/cli/help_doc.py +63 -0
- pyfect/cli/param.py +789 -0
- pyfect/cli/primitive.py +308 -0
- pyfect/cli/prompt/__init__.py +75 -0
- pyfect/cli/prompt/_auto_complete.py +205 -0
- pyfect/cli/prompt/_choices.py +104 -0
- pyfect/cli/prompt/_confirm.py +195 -0
- pyfect/cli/prompt/_core.py +512 -0
- pyfect/cli/prompt/_date.py +173 -0
- pyfect/cli/prompt/_date_parts.py +387 -0
- pyfect/cli/prompt/_file.py +372 -0
- pyfect/cli/prompt/_multi_select.py +204 -0
- pyfect/cli/prompt/_number.py +257 -0
- pyfect/cli/prompt/_select.py +117 -0
- pyfect/cli/prompt/_text.py +239 -0
- pyfect/cli/terminal_capabilities.py +118 -0
- pyfect/clock.py +150 -0
- pyfect/cluster/__init__.py +155 -0
- pyfect/cluster/_cluster_abandon.py +36 -0
- pyfect/cluster/_cluster_layer.py +122 -0
- pyfect/cluster/_entity_activation.py +16 -0
- pyfect/cluster/_entity_manager/__init__.py +22 -0
- pyfect/cluster/_entity_manager/_activation.py +540 -0
- pyfect/cluster/_entity_manager/_core.py +186 -0
- pyfect/cluster/_entity_manager/_decode.py +60 -0
- pyfect/cluster/_entity_manager/_manager.py +423 -0
- pyfect/cluster/_entity_manager/_state.py +113 -0
- pyfect/cluster/_entity_reaper.py +94 -0
- pyfect/cluster/_hash.py +13 -0
- pyfect/cluster/_interruptors.py +88 -0
- pyfect/cluster/_resource_map.py +179 -0
- pyfect/cluster/_resource_ref.py +266 -0
- pyfect/cluster/_shard_lock.py +14 -0
- pyfect/cluster/cluster_cron.py +230 -0
- pyfect/cluster/cluster_error.py +81 -0
- pyfect/cluster/cluster_http.py +210 -0
- pyfect/cluster/cluster_metrics.py +54 -0
- pyfect/cluster/cluster_schema.py +137 -0
- pyfect/cluster/cluster_socket.py +197 -0
- pyfect/cluster/cluster_workflow_engine/__init__.py +24 -0
- pyfect/cluster/cluster_workflow_engine/_engine.py +1099 -0
- pyfect/cluster/cluster_workflow_engine/_entities.py +298 -0
- pyfect/cluster/deliver_at.py +31 -0
- pyfect/cluster/entity/__init__.py +39 -0
- pyfect/cluster/entity/_entity.py +332 -0
- pyfect/cluster/entity/_handlers.py +168 -0
- pyfect/cluster/entity/_keep_alive.py +74 -0
- pyfect/cluster/entity/_queue.py +103 -0
- pyfect/cluster/entity/_request.py +34 -0
- pyfect/cluster/entity/_services.py +23 -0
- pyfect/cluster/entity/_test_client.py +237 -0
- pyfect/cluster/entity_address.py +25 -0
- pyfect/cluster/entity_id.py +20 -0
- pyfect/cluster/entity_proxy.py +161 -0
- pyfect/cluster/entity_proxy_server.py +103 -0
- pyfect/cluster/entity_resource.py +122 -0
- pyfect/cluster/entity_type.py +20 -0
- pyfect/cluster/envelope.py +307 -0
- pyfect/cluster/http_runner.py +254 -0
- pyfect/cluster/k8s_http_client.py +281 -0
- pyfect/cluster/k8s_types.py +1394 -0
- pyfect/cluster/machine_id.py +21 -0
- pyfect/cluster/message.py +219 -0
- pyfect/cluster/message_storage/__init__.py +67 -0
- pyfect/cluster/message_storage/_encoded.py +393 -0
- pyfect/cluster/message_storage/_memory.py +386 -0
- pyfect/cluster/message_storage/_noop.py +109 -0
- pyfect/cluster/message_storage/_service.py +388 -0
- pyfect/cluster/reply.py +236 -0
- pyfect/cluster/runner.py +32 -0
- pyfect/cluster/runner_address.py +23 -0
- pyfect/cluster/runner_health.py +93 -0
- pyfect/cluster/runner_server.py +286 -0
- pyfect/cluster/runner_storage.py +212 -0
- pyfect/cluster/runners/__init__.py +55 -0
- pyfect/cluster/runners/_local.py +460 -0
- pyfect/cluster/runners/_noop.py +57 -0
- pyfect/cluster/runners/_rpc.py +290 -0
- pyfect/cluster/runners/_service.py +113 -0
- pyfect/cluster/shard_id.py +39 -0
- pyfect/cluster/sharding/__init__.py +12 -0
- pyfect/cluster/sharding/_acquisition.py +490 -0
- pyfect/cluster/sharding/_clients.py +271 -0
- pyfect/cluster/sharding/_make.py +388 -0
- pyfect/cluster/sharding/_routing.py +296 -0
- pyfect/cluster/sharding/_runners_sync.py +198 -0
- pyfect/cluster/sharding/_service.py +91 -0
- pyfect/cluster/sharding/_singletons.py +183 -0
- pyfect/cluster/sharding/_state.py +296 -0
- pyfect/cluster/sharding/_storage_loop.py +421 -0
- pyfect/cluster/sharding_config.py +230 -0
- pyfect/cluster/sharding_registration_event.py +28 -0
- pyfect/cluster/single_runner.py +49 -0
- pyfect/cluster/singleton.py +36 -0
- pyfect/cluster/singleton_address.py +20 -0
- pyfect/cluster/snowflake.py +133 -0
- pyfect/cluster/socket_runner.py +67 -0
- pyfect/cluster/sql_message_storage.py +1001 -0
- pyfect/cluster/sql_runner_storage.py +841 -0
- pyfect/cluster/test_runner.py +23 -0
- pyfect/config/__init__.py +93 -0
- pyfect/config/_all.py +96 -0
- pyfect/config/_collections.py +291 -0
- pyfect/config/_config.py +261 -0
- pyfect/config/_constructors.py +106 -0
- pyfect/config/_error.py +27 -0
- pyfect/config/_leaf.py +92 -0
- pyfect/config/_scalars.py +193 -0
- pyfect/config/_schema.py +340 -0
- pyfect/config_provider/__init__.py +60 -0
- pyfect/config_provider/_dir.py +84 -0
- pyfect/config_provider/_dotenv.py +127 -0
- pyfect/config_provider/_env.py +79 -0
- pyfect/config_provider/_layers.py +80 -0
- pyfect/config_provider/_node.py +90 -0
- pyfect/config_provider/_provider.py +118 -0
- pyfect/config_provider/_unknown.py +62 -0
- pyfect/console/__init__.py +63 -0
- pyfect/console/_effects.py +134 -0
- pyfect/console/_live.py +156 -0
- pyfect/console/_service.py +49 -0
- pyfect/console/_table.py +90 -0
- pyfect/context.py +414 -0
- pyfect/cron/__init__.py +30 -0
- pyfect/cron/_model.py +183 -0
- pyfect/cron/_search.py +268 -0
- pyfect/cron/_text.py +202 -0
- pyfect/cron/_wall.py +42 -0
- pyfect/crypto.py +257 -0
- pyfect/date_time/__init__.py +77 -0
- pyfect/date_time/_calendar.py +209 -0
- pyfect/date_time/_current.py +118 -0
- pyfect/date_time/_epoch.py +22 -0
- pyfect/date_time/_iso.py +80 -0
- pyfect/date_time/_zones.py +135 -0
- pyfect/date_time/intl.py +144 -0
- pyfect/deferred.py +145 -0
- pyfect/devtools/__init__.py +24 -0
- pyfect/devtools/client.py +323 -0
- pyfect/devtools/layers.py +32 -0
- pyfect/devtools/schema.py +301 -0
- pyfect/devtools/server.py +72 -0
- pyfect/duration.py +295 -0
- pyfect/effect.py +565 -0
- pyfect/effectable.py +37 -0
- pyfect/encoding/__init__.py +62 -0
- pyfect/encoding/_archive.py +197 -0
- pyfect/encoding/_json_array_scanner.py +477 -0
- pyfect/encoding/base64.py +23 -0
- pyfect/encoding/base64_url.py +19 -0
- pyfect/encoding/encoding_error.py +17 -0
- pyfect/encoding/hex.py +28 -0
- pyfect/encoding/ini.py +188 -0
- pyfect/encoding/json_array.py +246 -0
- pyfect/encoding/ndjson.py +461 -0
- pyfect/encoding/schema_binary/__init__.py +257 -0
- pyfect/encoding/schema_binary/_channels.py +173 -0
- pyfect/encoding/schema_binary/_compile.py +584 -0
- pyfect/encoding/schema_binary/_decode.py +848 -0
- pyfect/encoding/schema_binary/_dictionary.py +90 -0
- pyfect/encoding/schema_binary/_encode.py +838 -0
- pyfect/encoding/schema_binary/_fingerprint.py +168 -0
- pyfect/encoding/schema_binary/_layout.py +350 -0
- pyfect/encoding/schema_binary/_literals.py +47 -0
- pyfect/encoding/schema_binary/_parser.py +163 -0
- pyfect/encoding/schema_binary/_reader.py +148 -0
- pyfect/encoding/schema_binary/_target.py +310 -0
- pyfect/encoding/schema_binary/_values.py +133 -0
- pyfect/encoding/schema_binary/_wire.py +253 -0
- pyfect/encoding/sse.py +476 -0
- pyfect/encoding/tar/__init__.py +396 -0
- pyfect/encoding/tar/_decoder.py +220 -0
- pyfect/encoding/tar/_encoder.py +83 -0
- pyfect/encoding/tar/_header.py +494 -0
- pyfect/encoding/tar/_model.py +82 -0
- pyfect/encoding/toml.py +375 -0
- pyfect/encoding/xml/__init__.py +222 -0
- pyfect/encoding/xml/_chars.py +119 -0
- pyfect/encoding/xml/_collector.py +106 -0
- pyfect/encoding/xml/_inputs.py +126 -0
- pyfect/encoding/xml/_model.py +114 -0
- pyfect/encoding/xml/_parser.py +694 -0
- pyfect/encoding/xml/_scanner.py +180 -0
- pyfect/encoding/xml/_writer.py +238 -0
- pyfect/encoding/yaml.py +739 -0
- pyfect/encoding/zip/__init__.py +392 -0
- pyfect/encoding/zip/_decoder.py +378 -0
- pyfect/encoding/zip/_format.py +122 -0
- pyfect/encoding/zip/_inflate.py +65 -0
- pyfect/encoding/zip/_model.py +86 -0
- pyfect/encoding/zip/_writer.py +311 -0
- pyfect/error_reporter.py +240 -0
- pyfect/errors.py +321 -0
- pyfect/eventlog/__init__.py +77 -0
- pyfect/eventlog/_conflicts.py +94 -0
- pyfect/eventlog/_crypto.py +64 -0
- pyfect/eventlog/_identity.py +26 -0
- pyfect/eventlog/_identity_derivation.py +79 -0
- pyfect/eventlog/_transferable_bytes.py +55 -0
- pyfect/eventlog/_uuid.py +33 -0
- pyfect/eventlog/event.py +115 -0
- pyfect/eventlog/event_group.py +55 -0
- pyfect/eventlog/event_journal.py +630 -0
- pyfect/eventlog/event_log.py +1039 -0
- pyfect/eventlog/event_log_encryption.py +219 -0
- pyfect/eventlog/event_log_message.py +357 -0
- pyfect/eventlog/event_log_remote.py +465 -0
- pyfect/eventlog/event_log_server.py +442 -0
- pyfect/eventlog/event_log_server_encrypted.py +322 -0
- pyfect/eventlog/event_log_server_unencrypted.py +814 -0
- pyfect/eventlog/event_log_session_auth.py +250 -0
- pyfect/eventlog/event_row.py +423 -0
- pyfect/eventlog/sql_event_journal.py +692 -0
- pyfect/eventlog/sql_event_log_server_encrypted.py +440 -0
- pyfect/eventlog/sql_event_log_server_unencrypted.py +568 -0
- pyfect/execution_plan/__init__.py +42 -0
- pyfect/execution_plan/_events.py +128 -0
- pyfect/execution_plan/_model.py +108 -0
- pyfect/execution_plan/_run.py +176 -0
- pyfect/exit.py +205 -0
- pyfect/fiber_handle.py +305 -0
- pyfect/fiber_map.py +350 -0
- pyfect/fiber_set.py +232 -0
- pyfect/file_system/__init__.py +69 -0
- pyfect/file_system/_copy.py +116 -0
- pyfect/file_system/_errors.py +32 -0
- pyfect/file_system/_file.py +177 -0
- pyfect/file_system/_live.py +351 -0
- pyfect/file_system/_model.py +72 -0
- pyfect/file_system/_noop.py +189 -0
- pyfect/file_system/_service.py +336 -0
- pyfect/file_system/_stat.py +69 -0
- pyfect/file_system/_watch.py +107 -0
- pyfect/filters.py +245 -0
- pyfect/graph/__init__.py +101 -0
- pyfect/graph/_all_pairs.py +150 -0
- pyfect/graph/_combine.py +259 -0
- pyfect/graph/_compact.py +143 -0
- pyfect/graph/_components.py +200 -0
- pyfect/graph/_cycles.py +144 -0
- pyfect/graph/_enumerate.py +168 -0
- pyfect/graph/_errors.py +51 -0
- pyfect/graph/_flow.py +164 -0
- pyfect/graph/_forest.py +109 -0
- pyfect/graph/_graph.py +269 -0
- pyfect/graph/_lowlink.py +98 -0
- pyfect/graph/_matching.py +101 -0
- pyfect/graph/_mutate.py +211 -0
- pyfect/graph/_query.py +138 -0
- pyfect/graph/_records.py +119 -0
- pyfect/graph/_render.py +109 -0
- pyfect/graph/_shortest.py +274 -0
- pyfect/graph/_snapshot.py +50 -0
- pyfect/graph/_store.py +136 -0
- pyfect/graph/_traverse.py +289 -0
- pyfect/graph/_view.py +384 -0
- pyfect/graph/_walker.py +70 -0
- pyfect/graphql/__init__.py +87 -0
- pyfect/graphql/_codec.py +117 -0
- pyfect/graphql/_http.py +309 -0
- pyfect/graphql/_schemas.py +39 -0
- pyfect/graphql/_slices.py +110 -0
- pyfect/graphql/_template.py +111 -0
- pyfect/graphql/_types.py +115 -0
- pyfect/graphql/_ws.py +395 -0
- pyfect/graphql/_ws_messages.py +120 -0
- pyfect/graphql/client.py +428 -0
- pyfect/graphql/client_error.py +285 -0
- pyfect/graphql/group.py +142 -0
- pyfect/graphql/middleware.py +98 -0
- pyfect/graphql/operation.py +409 -0
- pyfect/graphql/protocol.py +119 -0
- pyfect/graphql/resolver.py +574 -0
- pyfect/graphql/template.py +61 -0
- pyfect/hash_map.py +317 -0
- pyfect/hash_ring.py +206 -0
- pyfect/hash_set.py +163 -0
- pyfect/http/__init__.py +125 -0
- pyfect/http/_asgi_listener.py +272 -0
- pyfect/http/_authority.py +63 -0
- pyfect/http/_brotli.py +27 -0
- pyfect/http/_compression.py +52 -0
- pyfect/http/_field.py +18 -0
- pyfect/http/_foreign_path.py +27 -0
- pyfect/http/_form_data.py +34 -0
- pyfect/http/_httpx_options.py +120 -0
- pyfect/http/_in_memory.py +76 -0
- pyfect/http/_request_state.py +59 -0
- pyfect/http/_vary.py +14 -0
- pyfect/http/abort.py +65 -0
- pyfect/http/asgi/__init__.py +59 -0
- pyfect/http/asgi/_app.py +155 -0
- pyfect/http/asgi/_http.py +201 -0
- pyfect/http/asgi/_mount.py +263 -0
- pyfect/http/asgi/_scope.py +86 -0
- pyfect/http/asgi/_serve.py +72 -0
- pyfect/http/asgi/_types.py +13 -0
- pyfect/http/asgi/_web_socket.py +325 -0
- pyfect/http/body.py +437 -0
- pyfect/http/client/__init__.py +111 -0
- pyfect/http/client/_circuit_breaker.py +113 -0
- pyfect/http/client/_cookie_jar.py +120 -0
- pyfect/http/client/_make.py +252 -0
- pyfect/http/client/_rate_limit.py +423 -0
- pyfect/http/client/_redirects.py +93 -0
- pyfect/http/client/_retry.py +115 -0
- pyfect/http/client/_service.py +497 -0
- pyfect/http/cookies.py +444 -0
- pyfect/http/error.py +188 -0
- pyfect/http/etag.py +96 -0
- pyfect/http/file_like.py +49 -0
- pyfect/http/find_my_way/__init__.py +10 -0
- pyfect/http/find_my_way/_nodes.py +186 -0
- pyfect/http/find_my_way/_paths.py +43 -0
- pyfect/http/find_my_way/_query_string.py +67 -0
- pyfect/http/find_my_way/_router.py +401 -0
- pyfect/http/headers.py +343 -0
- pyfect/http/http_effect.py +198 -0
- pyfect/http/httpx_client.py +291 -0
- pyfect/http/hypercorn_server.py +122 -0
- pyfect/http/incoming_message.py +140 -0
- pyfect/http/live_server/__init__.py +191 -0
- pyfect/http/live_server/_body.py +164 -0
- pyfect/http/live_server/_exchange.py +321 -0
- pyfect/http/live_server/_head.py +194 -0
- pyfect/http/live_server/_inbound.py +156 -0
- pyfect/http/live_server/_listener.py +211 -0
- pyfect/http/live_server/_serve.py +225 -0
- pyfect/http/live_server/_timers.py +20 -0
- pyfect/http/live_server/_upgrade.py +108 -0
- pyfect/http/live_server/_websocket.py +286 -0
- pyfect/http/live_server/_ws_frames.py +242 -0
- pyfect/http/method.py +30 -0
- pyfect/http/middleware/__init__.py +61 -0
- pyfect/http/middleware/_compression.py +156 -0
- pyfect/http/middleware/_cors.py +129 -0
- pyfect/http/middleware/_decompression.py +274 -0
- pyfect/http/middleware/_forwarded.py +113 -0
- pyfect/http/middleware/_logger.py +73 -0
- pyfect/http/middleware/_search_params.py +34 -0
- pyfect/http/middleware/_tracer.py +153 -0
- pyfect/http/middleware/_types.py +16 -0
- pyfect/http/mime/__init__.py +69 -0
- pyfect/http/mime/_types.py +361 -0
- pyfect/http/multipart.py +613 -0
- pyfect/http/multipart_parser/__init__.py +348 -0
- pyfect/http/multipart_parser/_content_type.py +109 -0
- pyfect/http/multipart_parser/_headers.py +206 -0
- pyfect/http/multipart_parser/_search.py +85 -0
- pyfect/http/origin.py +146 -0
- pyfect/http/platform.py +593 -0
- pyfect/http/prometheus.py +34 -0
- pyfect/http/request.py +384 -0
- pyfect/http/response.py +474 -0
- pyfect/http/router/__init__.py +82 -0
- pyfect/http/router/_api.py +462 -0
- pyfect/http/router/_in_memory.py +76 -0
- pyfect/http/router/_markers.py +47 -0
- pyfect/http/router/_middleware.py +181 -0
- pyfect/http/router/_middleware_stack.py +51 -0
- pyfect/http/router/_provided.py +31 -0
- pyfect/http/router/_route.py +114 -0
- pyfect/http/router/_router.py +308 -0
- pyfect/http/schema.py +276 -0
- pyfect/http/server.py +199 -0
- pyfect/http/server_error.py +261 -0
- pyfect/http/server_request.py +729 -0
- pyfect/http/server_respondable.py +77 -0
- pyfect/http/server_response.py +569 -0
- pyfect/http/static_server/__init__.py +257 -0
- pyfect/http/static_server/_conditional.py +72 -0
- pyfect/http/static_server/_mime.py +55 -0
- pyfect/http/static_server/_range.py +53 -0
- pyfect/http/status.py +51 -0
- pyfect/http/template.py +161 -0
- pyfect/http/trace_context.py +91 -0
- pyfect/http/url.py +442 -0
- pyfect/http/url_params.py +304 -0
- pyfect/http/uvicorn_server.py +119 -0
- pyfect/http/web.py +171 -0
- pyfect/http/web_handler.py +463 -0
- pyfect/http/wire.py +209 -0
- pyfect/http_api/__init__.py +83 -0
- pyfect/http_api/_assets/scalar.js +8 -0
- pyfect/http_api/_assets/swagger-ui.css +3 -0
- pyfect/http_api/_assets/swagger-ui.js +4 -0
- pyfect/http_api/_client_method.py +220 -0
- pyfect/http_api/_client_payload.py +144 -0
- pyfect/http_api/_client_response.py +377 -0
- pyfect/http_api/_handlers.py +282 -0
- pyfect/http_api/_html.py +38 -0
- pyfect/http_api/_media_type.py +6 -0
- pyfect/http_api/_openapi_responses.py +164 -0
- pyfect/http_api/_openapi_schemas.py +163 -0
- pyfect/http_api/_path.py +102 -0
- pyfect/http_api/_payload.py +260 -0
- pyfect/http_api/_response.py +363 -0
- pyfect/http_api/_security.py +174 -0
- pyfect/http_api/api.py +265 -0
- pyfect/http_api/builder.py +447 -0
- pyfect/http_api/client.py +474 -0
- pyfect/http_api/endpoint.py +969 -0
- pyfect/http_api/error.py +280 -0
- pyfect/http_api/group.py +215 -0
- pyfect/http_api/middleware.py +232 -0
- pyfect/http_api/openapi.py +396 -0
- pyfect/http_api/scalar.py +143 -0
- pyfect/http_api/schema.py +713 -0
- pyfect/http_api/security.py +91 -0
- pyfect/http_api/swagger.py +69 -0
- pyfect/http_api/test.py +105 -0
- pyfect/json_patch.py +255 -0
- pyfect/json_pointer.py +100 -0
- pyfect/json_schema/__init__.py +66 -0
- pyfect/json_schema/_api.py +96 -0
- pyfect/json_schema/_converter.py +258 -0
- pyfect/json_schema/_draft04.py +198 -0
- pyfect/json_schema/_draft07.py +203 -0
- pyfect/json_schema/_keywords.py +202 -0
- pyfect/json_schema/_model.py +201 -0
- pyfect/json_schema/_openapi.py +208 -0
- pyfect/latch.py +127 -0
- pyfect/layer.py +479 -0
- pyfect/layer_map.py +279 -0
- pyfect/layer_ref.py +208 -0
- pyfect/logger/__init__.py +91 -0
- pyfect/logger/_batched.py +49 -0
- pyfect/logger/_console.py +116 -0
- pyfect/logger/_file.py +31 -0
- pyfect/logger/_formats.py +104 -0
- pyfect/logger/_layer.py +50 -0
- pyfect/logger/_level.py +53 -0
- pyfect/logger/_log.py +142 -0
- pyfect/logger/_model.py +163 -0
- pyfect/logger/_refs.py +78 -0
- pyfect/logger/_stdlib.py +54 -0
- pyfect/logger/_text.py +263 -0
- pyfect/logger/_tracer.py +35 -0
- pyfect/managed_runtime.py +393 -0
- pyfect/metric/__init__.py +119 -0
- pyfect/metric/_boundaries.py +27 -0
- pyfect/metric/_kinds.py +417 -0
- pyfect/metric/_metric.py +302 -0
- pyfect/metric/_prometheus.py +150 -0
- pyfect/metric/_refs.py +78 -0
- pyfect/metric/_runtime.py +75 -0
- pyfect/metric/_snapshot.py +97 -0
- pyfect/metric/_state.py +74 -0
- pyfect/msgspec.py +98 -0
- pyfect/mutable_hash_map.py +140 -0
- pyfect/mutable_hash_set.py +95 -0
- pyfect/net/__init__.py +18 -0
- pyfect/net/_errors.py +29 -0
- pyfect/net/address_resolver.py +179 -0
- pyfect/net/dns/__init__.py +92 -0
- pyfect/net/dns/_error.py +43 -0
- pyfect/net/dns/_records.py +335 -0
- pyfect/net/dns/_service.py +287 -0
- pyfect/net/dns/_system.py +89 -0
- pyfect/net/host.py +311 -0
- pyfect/net/ip_interface.py +202 -0
- pyfect/net/ip_network.py +250 -0
- pyfect/net/net_address/__init__.py +245 -0
- pyfect/net/net_address/_inet.py +412 -0
- pyfect/net/net_address/_ip.py +348 -0
- pyfect/net/net_address/_mac.py +119 -0
- pyfect/optic/__init__.py +50 -0
- pyfect/optic/_builtins.py +90 -0
- pyfect/optic/_data.py +97 -0
- pyfect/optic/_optic.py +422 -0
- pyfect/optic/_steps.py +151 -0
- pyfect/option.py +362 -0
- pyfect/otel/__init__.py +22 -0
- pyfect/otel/_attributes.py +67 -0
- pyfect/otel/_producer.py +322 -0
- pyfect/otel/_shutdown.py +26 -0
- pyfect/otel/_span.py +292 -0
- pyfect/otel/_wrapper.py +92 -0
- pyfect/otel/logger.py +154 -0
- pyfect/otel/metrics.py +126 -0
- pyfect/otel/resource.py +111 -0
- pyfect/otel/sdk.py +195 -0
- pyfect/otel/tracer.py +157 -0
- pyfect/partitioned_semaphore.py +211 -0
- pyfect/path.py +304 -0
- pyfect/persistence/__init__.py +77 -0
- pyfect/persistence/_columns.py +32 -0
- pyfect/persistence/_error.py +14 -0
- pyfect/persistence/_seconds.py +68 -0
- pyfect/persistence/_sql_keys.py +47 -0
- pyfect/persistence/circuit_breaker/__init__.py +120 -0
- pyfect/persistence/circuit_breaker/_breaker.py +317 -0
- pyfect/persistence/circuit_breaker/_errors.py +67 -0
- pyfect/persistence/circuit_breaker/_memory.py +243 -0
- pyfect/persistence/circuit_breaker/_redis.py +509 -0
- pyfect/persistence/circuit_breaker/_store.py +60 -0
- pyfect/persistence/circuit_breaker/_types.py +191 -0
- pyfect/persistence/key_value_store/__init__.py +51 -0
- pyfect/persistence/key_value_store/_layers.py +216 -0
- pyfect/persistence/key_value_store/_schema_store.py +91 -0
- pyfect/persistence/key_value_store/_sql.py +197 -0
- pyfect/persistence/key_value_store/_store.py +212 -0
- pyfect/persistence/persistable.py +164 -0
- pyfect/persistence/persisted_cache.py +159 -0
- pyfect/persistence/persisted_queue/__init__.py +75 -0
- pyfect/persistence/persisted_queue/_error.py +42 -0
- pyfect/persistence/persisted_queue/_mailbox.py +101 -0
- pyfect/persistence/persisted_queue/_memory.py +182 -0
- pyfect/persistence/persisted_queue/_queue.py +272 -0
- pyfect/persistence/persisted_queue/_redis.py +455 -0
- pyfect/persistence/persisted_queue/_redis_scripts.py +395 -0
- pyfect/persistence/persisted_queue/_sql.py +334 -0
- pyfect/persistence/persisted_queue/_sql_migrations.py +319 -0
- pyfect/persistence/persisted_queue/_sql_statements.py +441 -0
- pyfect/persistence/persisted_queue/_store.py +147 -0
- pyfect/persistence/persistence/__init__.py +84 -0
- pyfect/persistence/persistence/_kvs.py +135 -0
- pyfect/persistence/persistence/_memory.py +62 -0
- pyfect/persistence/persistence/_redis.py +162 -0
- pyfect/persistence/persistence/_service.py +276 -0
- pyfect/persistence/persistence/_sql.py +499 -0
- pyfect/persistence/rate_limiter/__init__.py +82 -0
- pyfect/persistence/rate_limiter/_errors.py +59 -0
- pyfect/persistence/rate_limiter/_limiter.py +325 -0
- pyfect/persistence/rate_limiter/_memory.py +217 -0
- pyfect/persistence/rate_limiter/_redis.py +485 -0
- pyfect/persistence/rate_limiter/_store.py +89 -0
- pyfect/persistence/redis/__init__.py +52 -0
- pyfect/persistence/redis/_error.py +12 -0
- pyfect/persistence/redis/_make.py +234 -0
- pyfect/persistence/redis/_script.py +78 -0
- pyfect/persistence/redis/_service.py +104 -0
- pyfect/persistence/redis_py.py +303 -0
- pyfect/persistence/user_directories.py +195 -0
- pyfect/platform.py +26 -0
- pyfect/platform_error.py +256 -0
- pyfect/pool/__init__.py +36 -0
- pyfect/pool/_pool.py +707 -0
- pyfect/pool/_state.py +161 -0
- pyfect/pool/_ttl.py +105 -0
- pyfect/pool/_waiters.py +116 -0
- pyfect/primary_key.py +35 -0
- pyfect/process/__init__.py +82 -0
- pyfect/process/_child.py +213 -0
- pyfect/process/_command.py +358 -0
- pyfect/process/_handle.py +77 -0
- pyfect/process/_live.py +455 -0
- pyfect/process/_os.py +78 -0
- pyfect/process/_pipes.py +291 -0
- pyfect/process/_spawner.py +75 -0
- pyfect/pubsub/__init__.py +33 -0
- pyfect/pubsub/_atomic.py +181 -0
- pyfect/pubsub/_hub.py +214 -0
- pyfect/pubsub/_pubsub.py +214 -0
- pyfect/pubsub/_replay.py +107 -0
- pyfect/pubsub/_subscription.py +226 -0
- pyfect/pull.py +132 -0
- pyfect/py.typed +0 -0
- pyfect/pydantic.py +188 -0
- pyfect/queue/__init__.py +33 -0
- pyfect/queue/_queue.py +525 -0
- pyfect/queue/_state.py +111 -0
- pyfect/queue/_views.py +55 -0
- pyfect/random.py +146 -0
- pyfect/rc_map.py +363 -0
- pyfect/rc_ref.py +271 -0
- pyfect/reactivity/__init__.py +53 -0
- pyfect/reactivity/_async_result_schema.py +227 -0
- pyfect/reactivity/_hydration.py +18 -0
- pyfect/reactivity/_live.py +21 -0
- pyfect/reactivity/_reactivity.py +256 -0
- pyfect/reactivity/_result_effect.py +17 -0
- pyfect/reactivity/_time.py +11 -0
- pyfect/reactivity/async_result.py +679 -0
- pyfect/reactivity/atom/__init__.py +143 -0
- pyfect/reactivity/atom/_combinators.py +223 -0
- pyfect/reactivity/atom/_constructors.py +80 -0
- pyfect/reactivity/atom/_conversions.py +79 -0
- pyfect/reactivity/atom/_effect.py +252 -0
- pyfect/reactivity/atom/_fn.py +341 -0
- pyfect/reactivity/atom/_kvs.py +101 -0
- pyfect/reactivity/atom/_optimistic.py +162 -0
- pyfect/reactivity/atom/_runtime.py +323 -0
- pyfect/reactivity/atom/_serializable.py +65 -0
- pyfect/reactivity/atom/_subscription_ref.py +102 -0
- pyfect/reactivity/atom/_swr.py +69 -0
- pyfect/reactivity/atom/_types.py +435 -0
- pyfect/reactivity/atom_http_api.py +667 -0
- pyfect/reactivity/atom_ref.py +260 -0
- pyfect/reactivity/atom_registry/__init__.py +219 -0
- pyfect/reactivity/atom_registry/_batch.py +96 -0
- pyfect/reactivity/atom_registry/_dispatcher.py +60 -0
- pyfect/reactivity/atom_registry/_lifetime.py +202 -0
- pyfect/reactivity/atom_registry/_node.py +423 -0
- pyfect/reactivity/atom_registry/_registry.py +380 -0
- pyfect/reactivity/atom_rpc.py +283 -0
- pyfect/reactivity/hydration.py +200 -0
- pyfect/redacted.py +83 -0
- pyfect/ref.py +129 -0
- pyfect/resource.py +98 -0
- pyfect/result.py +306 -0
- pyfect/rpc/__init__.py +64 -0
- pyfect/rpc/_encoded_schema.py +86 -0
- pyfect/rpc/_json_rpc.py +246 -0
- pyfect/rpc/_methods.py +177 -0
- pyfect/rpc/_rpc.py +498 -0
- pyfect/rpc/client/__init__.py +100 -0
- pyfect/rpc/client/_core.py +449 -0
- pyfect/rpc/client/_http.py +257 -0
- pyfect/rpc/client/_protocol.py +171 -0
- pyfect/rpc/client/_serialized.py +321 -0
- pyfect/rpc/client/_transports.py +328 -0
- pyfect/rpc/client/_worker.py +283 -0
- pyfect/rpc/client_error.py +35 -0
- pyfect/rpc/group.py +296 -0
- pyfect/rpc/message.py +299 -0
- pyfect/rpc/middleware.py +182 -0
- pyfect/rpc/schema.py +89 -0
- pyfect/rpc/serialization.py +366 -0
- pyfect/rpc/server/__init__.py +181 -0
- pyfect/rpc/server/_core.py +549 -0
- pyfect/rpc/server/_protocol.py +123 -0
- pyfect/rpc/server/_serialized.py +363 -0
- pyfect/rpc/server/_stdio.py +101 -0
- pyfect/rpc/server/_transports.py +427 -0
- pyfect/rpc/server/_worker.py +108 -0
- pyfect/rpc/test.py +74 -0
- pyfect/rpc/worker.py +92 -0
- pyfect/run.py +393 -0
- pyfect/schedule/__init__.py +91 -0
- pyfect/schedule/_combinators.py +168 -0
- pyfect/schedule/_constructors.py +223 -0
- pyfect/schedule/_cron.py +49 -0
- pyfect/schedule/_drive.py +191 -0
- pyfect/schedule/_model.py +405 -0
- pyfect/scheduler.py +575 -0
- pyfect/schema/__init__.py +866 -0
- pyfect/schema/_adapters.py +39 -0
- pyfect/schema/_arbitrary_constraint.py +242 -0
- pyfect/schema/_arbitrary_graph.py +325 -0
- pyfect/schema/_arbitrary_links.py +179 -0
- pyfect/schema/_arbitrary_number.py +112 -0
- pyfect/schema/_arrays.py +287 -0
- pyfect/schema/_assignable.py +118 -0
- pyfect/schema/_boolean_literals.py +31 -0
- pyfect/schema/_checks_number.py +216 -0
- pyfect/schema/_checks_order.py +140 -0
- pyfect/schema/_checks_size.py +277 -0
- pyfect/schema/_checks_string.py +241 -0
- pyfect/schema/_class.py +569 -0
- pyfect/schema/_codec_derivation.py +227 -0
- pyfect/schema/_codec_links.py +221 -0
- pyfect/schema/_codecs.py +316 -0
- pyfect/schema/_collections.py +172 -0
- pyfect/schema/_constructor_default.py +89 -0
- pyfect/schema/_data.py +440 -0
- pyfect/schema/_data_codecs.py +145 -0
- pyfect/schema/_decimal_regexp.py +243 -0
- pyfect/schema/_declare.py +68 -0
- pyfect/schema/_derive.py +292 -0
- pyfect/schema/_duration_codecs.py +27 -0
- pyfect/schema/_ecma_pattern.py +416 -0
- pyfect/schema/_error.py +26 -0
- pyfect/schema/_error_instance.py +222 -0
- pyfect/schema/_graph.py +271 -0
- pyfect/schema/_instances.py +240 -0
- pyfect/schema/_jsonschema/__init__.py +90 -0
- pyfect/schema/_jsonschema/_annotations.py +114 -0
- pyfect/schema/_jsonschema/_checks.py +253 -0
- pyfect/schema/_jsonschema/_compile.py +346 -0
- pyfect/schema/_jsonschema/_exact.py +19 -0
- pyfect/schema/_jsonschema/_merge.py +211 -0
- pyfect/schema/_jsonschema/_objects.py +231 -0
- pyfect/schema/_jsonschema/_references.py +221 -0
- pyfect/schema/_jsonschema/_template.py +69 -0
- pyfect/schema/_literals.py +147 -0
- pyfect/schema/_middleware.py +209 -0
- pyfect/schema/_net.py +522 -0
- pyfect/schema/_options.py +49 -0
- pyfect/schema/_parse.py +351 -0
- pyfect/schema/_parsed_text.py +37 -0
- pyfect/schema/_primitives.py +48 -0
- pyfect/schema/_record.py +41 -0
- pyfect/schema/_refine.py +81 -0
- pyfect/schema/_schema.py +158 -0
- pyfect/schema/_semver.py +144 -0
- pyfect/schema/_strategy/__init__.py +258 -0
- pyfect/schema/_strategy/_big_decimal.py +203 -0
- pyfect/schema/_strategy/_composites.py +119 -0
- pyfect/schema/_strategy/_leaves.py +94 -0
- pyfect/schema/_strategy/_net.py +133 -0
- pyfect/schema/_struct.py +87 -0
- pyfect/schema/_struct_keys.py +128 -0
- pyfect/schema/_tagged.py +254 -0
- pyfect/schema/_template_literal.py +172 -0
- pyfect/schema/_to_codec_array_from_single.py +115 -0
- pyfect/schema/_to_codec_go_json.py +174 -0
- pyfect/schema/_to_codec_json.py +73 -0
- pyfect/schema/_to_codec_string_tree.py +115 -0
- pyfect/schema/_to_differ_json_patch.py +44 -0
- pyfect/schema/_to_encoder_xml.py +119 -0
- pyfect/schema/_to_equivalence.py +186 -0
- pyfect/schema/_to_formatter.py +180 -0
- pyfect/schema/_to_iso.py +73 -0
- pyfect/schema/_to_strategy.py +31 -0
- pyfect/schema/_transform.py +210 -0
- pyfect/schema/_tree.py +27 -0
- pyfect/schema/_type_walk.py +53 -0
- pyfect/schema/_undefined.py +58 -0
- pyfect/schema/_value_codecs.py +84 -0
- pyfect/schema/_values.py +123 -0
- pyfect/schema/_void.py +86 -0
- pyfect/schema/ast/__init__.py +227 -0
- pyfect/schema/ast/_annotations.py +43 -0
- pyfect/schema/ast/_arrays.py +210 -0
- pyfect/schema/ast/_base.py +295 -0
- pyfect/schema/ast/_candidates.py +309 -0
- pyfect/schema/ast/_check.py +181 -0
- pyfect/schema/ast/_class.py +159 -0
- pyfect/schema/ast/_constructors.py +65 -0
- pyfect/schema/ast/_declaration.py +97 -0
- pyfect/schema/ast/_encoding.py +111 -0
- pyfect/schema/ast/_json.py +129 -0
- pyfect/schema/ast/_keywords.py +125 -0
- pyfect/schema/ast/_literal.py +157 -0
- pyfect/schema/ast/_objects.py +356 -0
- pyfect/schema/ast/_record.py +36 -0
- pyfect/schema/ast/_result.py +116 -0
- pyfect/schema/ast/_steps.py +209 -0
- pyfect/schema/ast/_suspend.py +65 -0
- pyfect/schema/ast/_template_literal.py +256 -0
- pyfect/schema/ast/_union.py +172 -0
- pyfect/schema/getter/__init__.py +136 -0
- pyfect/schema/getter/_binary.py +104 -0
- pyfect/schema/getter/_coerce.py +196 -0
- pyfect/schema/getter/_getter.py +243 -0
- pyfect/schema/getter/_make.py +151 -0
- pyfect/schema/getter/_text.py +141 -0
- pyfect/schema/getter/_tree_record.py +170 -0
- pyfect/schema/issue.py +420 -0
- pyfect/schema/model/__init__.py +120 -0
- pyfect/schema/model/_class.py +245 -0
- pyfect/schema/model/_dates.py +103 -0
- pyfect/schema/model/_fields.py +77 -0
- pyfect/schema/model/_toolkit.py +114 -0
- pyfect/schema/model/_uuid.py +69 -0
- pyfect/schema/representation/__init__.py +424 -0
- pyfect/schema/representation/_builtin_revivers.py +331 -0
- pyfect/schema/representation/_code.py +106 -0
- pyfect/schema/representation/_code_builtins.py +281 -0
- pyfect/schema/representation/_code_document.py +563 -0
- pyfect/schema/representation/_code_sort.py +111 -0
- pyfect/schema/representation/_from.py +278 -0
- pyfect/schema/representation/_import/__init__.py +78 -0
- pyfect/schema/representation/_import/_finalize.py +121 -0
- pyfect/schema/representation/_import/_intersect.py +467 -0
- pyfect/schema/representation/_import/_js_order.py +66 -0
- pyfect/schema/representation/_import/_keywords.py +224 -0
- pyfect/schema/representation/_import/_nodes.py +199 -0
- pyfect/schema/representation/_import/_translate.py +455 -0
- pyfect/schema/representation/_json.py +520 -0
- pyfect/schema/representation/_json_schema.py +178 -0
- pyfect/schema/representation/_model.py +284 -0
- pyfect/schema/representation/_revivers.py +89 -0
- pyfect/schema/representation/_to.py +249 -0
- pyfect/schema/transformation/__init__.py +99 -0
- pyfect/schema/transformation/_builtins.py +314 -0
- pyfect/schema/transformation/_durations.py +150 -0
- pyfect/schema/transformation/_transformation.py +177 -0
- pyfect/schema/variant_schema/__init__.py +69 -0
- pyfect/schema/variant_schema/_class.py +188 -0
- pyfect/schema/variant_schema/_defaults.py +30 -0
- pyfect/schema/variant_schema/_field.py +32 -0
- pyfect/schema/variant_schema/_struct.py +130 -0
- pyfect/schema/variant_schema/_toolkit.py +160 -0
- pyfect/scope.py +374 -0
- pyfect/scoped_ref.py +124 -0
- pyfect/semaphore.py +276 -0
- pyfect/semver.py +403 -0
- pyfect/semver_range.py +420 -0
- pyfect/sink/__init__.py +131 -0
- pyfect/sink/_combinators.py +151 -0
- pyfect/sink/_constructors.py +187 -0
- pyfect/sink/_context.py +87 -0
- pyfect/sink/_folds.py +336 -0
- pyfect/sink/_for_each.py +87 -0
- pyfect/sink/_sink.py +265 -0
- pyfect/sink/_steps.py +34 -0
- pyfect/sink/_taking.py +183 -0
- pyfect/socket/__init__.py +146 -0
- pyfect/socket/_accepted.py +65 -0
- pyfect/socket/_connection.py +384 -0
- pyfect/socket/_errors.py +135 -0
- pyfect/socket/_loop.py +66 -0
- pyfect/socket/_net.py +281 -0
- pyfect/socket/_socket.py +245 -0
- pyfect/socket/_tls.py +88 -0
- pyfect/socket/_tls_session.py +117 -0
- pyfect/socket/_web_socket.py +555 -0
- pyfect/socket/datagram/__init__.py +110 -0
- pyfect/socket/datagram/_core.py +460 -0
- pyfect/socket/datagram/_errors.py +102 -0
- pyfect/socket/datagram/_live.py +675 -0
- pyfect/socket/datagram/_model.py +217 -0
- pyfect/socket/server.py +258 -0
- pyfect/socket/ws.py +484 -0
- pyfect/sql/__init__.py +60 -0
- pyfect/sql/_loop.py +134 -0
- pyfect/sql/clickhouse/__init__.py +74 -0
- pyfect/sql/clickhouse/_connection.py +237 -0
- pyfect/sql/clickhouse/_rows.py +87 -0
- pyfect/sql/clickhouse/client.py +322 -0
- pyfect/sql/clickhouse/config.py +241 -0
- pyfect/sql/clickhouse/driver.py +434 -0
- pyfect/sql/clickhouse/error.py +106 -0
- pyfect/sql/clickhouse/migrator.py +47 -0
- pyfect/sql/clickhouse/params.py +168 -0
- pyfect/sql/client/__init__.py +35 -0
- pyfect/sql/client/_client.py +309 -0
- pyfect/sql/client/_sqlite_acquirers.py +130 -0
- pyfect/sql/client/_transaction.py +410 -0
- pyfect/sql/connection.py +87 -0
- pyfect/sql/d1/__init__.py +48 -0
- pyfect/sql/d1/api.py +270 -0
- pyfect/sql/d1/client.py +344 -0
- pyfect/sql/d1/config.py +55 -0
- pyfect/sql/error.py +311 -0
- pyfect/sql/libsql/__init__.py +58 -0
- pyfect/sql/libsql/_connection.py +110 -0
- pyfect/sql/libsql/_hrana.py +258 -0
- pyfect/sql/libsql/_local.py +251 -0
- pyfect/sql/libsql/_targets.py +28 -0
- pyfect/sql/libsql/_values.py +46 -0
- pyfect/sql/libsql/client.py +225 -0
- pyfect/sql/libsql/config.py +170 -0
- pyfect/sql/libsql/migrator.py +43 -0
- pyfect/sql/libsql/sdk.py +123 -0
- pyfect/sql/migrator.py +434 -0
- pyfect/sql/model.py +324 -0
- pyfect/sql/mssql/__init__.py +79 -0
- pyfect/sql/mssql/_connection.py +169 -0
- pyfect/sql/mssql/_driver.py +103 -0
- pyfect/sql/mssql/_names.py +9 -0
- pyfect/sql/mssql/_pool.py +125 -0
- pyfect/sql/mssql/_tds.py +447 -0
- pyfect/sql/mssql/_tds_params.py +155 -0
- pyfect/sql/mssql/_worker.py +155 -0
- pyfect/sql/mssql/client.py +344 -0
- pyfect/sql/mssql/config.py +118 -0
- pyfect/sql/mssql/error.py +111 -0
- pyfect/sql/mssql/migrator.py +43 -0
- pyfect/sql/mssql/parameter.py +44 -0
- pyfect/sql/mssql/procedure.py +119 -0
- pyfect/sql/mssql/types.py +137 -0
- pyfect/sql/mysql/__init__.py +60 -0
- pyfect/sql/mysql/_asyncmy.py +168 -0
- pyfect/sql/mysql/_bind.py +76 -0
- pyfect/sql/mysql/_connection.py +156 -0
- pyfect/sql/mysql/_pool.py +63 -0
- pyfect/sql/mysql/_session.py +262 -0
- pyfect/sql/mysql/client.py +244 -0
- pyfect/sql/mysql/config.py +108 -0
- pyfect/sql/mysql/error.py +104 -0
- pyfect/sql/mysql/migrator.py +44 -0
- pyfect/sql/pg/__init__.py +82 -0
- pyfect/sql/pg/_resolve.py +284 -0
- pyfect/sql/pg/_session.py +380 -0
- pyfect/sql/pg/client.py +398 -0
- pyfect/sql/pg/config.py +142 -0
- pyfect/sql/pg/connection.py +585 -0
- pyfect/sql/pg/error.py +131 -0
- pyfect/sql/pg/migrator.py +139 -0
- pyfect/sql/pg/pool.py +265 -0
- pyfect/sql/resolver.py +294 -0
- pyfect/sql/result.py +24 -0
- pyfect/sql/schema.py +116 -0
- pyfect/sql/sqlite/__init__.py +26 -0
- pyfect/sql/sqlite/_connection.py +230 -0
- pyfect/sql/sqlite/_worker.py +71 -0
- pyfect/sql/sqlite/client.py +248 -0
- pyfect/sql/sqlite/migrator.py +43 -0
- pyfect/sql/statement/__init__.py +144 -0
- pyfect/sql/statement/_compiler.py +384 -0
- pyfect/sql/statement/_segments.py +487 -0
- pyfect/sql/statement/_statement.py +526 -0
- pyfect/sql/statement/_transforms.py +93 -0
- pyfect/sql/stream.py +88 -0
- pyfect/stdio.py +206 -0
- pyfect/stream/__init__.py +151 -0
- pyfect/stream/_async_iterable.py +129 -0
- pyfect/stream/_base.py +69 -0
- pyfect/stream/_bridge_methods.py +139 -0
- pyfect/stream/_catch.py +135 -0
- pyfect/stream/_catch_reason.py +558 -0
- pyfect/stream/_channel_methods.py +61 -0
- pyfect/stream/_chunk_methods.py +169 -0
- pyfect/stream/_chunking.py +310 -0
- pyfect/stream/_concat.py +65 -0
- pyfect/stream/_constructors.py +341 -0
- pyfect/stream/_context.py +147 -0
- pyfect/stream/_error_methods.py +254 -0
- pyfect/stream/_execution_plan.py +199 -0
- pyfect/stream/_fanout.py +154 -0
- pyfect/stream/_fanout_methods.py +183 -0
- pyfect/stream/_filter_methods.py +203 -0
- pyfect/stream/_grouping.py +168 -0
- pyfect/stream/_many.py +65 -0
- pyfect/stream/_map_methods.py +233 -0
- pyfect/stream/_mapping.py +126 -0
- pyfect/stream/_run.py +114 -0
- pyfect/stream/_run_methods.py +114 -0
- pyfect/stream/_service_methods.py +36 -0
- pyfect/stream/_sink_methods.py +151 -0
- pyfect/stream/_sinking.py +277 -0
- pyfect/stream/_sources.py +124 -0
- pyfect/stream/_stream.py +162 -0
- pyfect/stream/_taking.py +330 -0
- pyfect/stream/_text.py +43 -0
- pyfect/stream/_timing.py +294 -0
- pyfect/stream/_timing_methods.py +252 -0
- pyfect/stream/_zip_methods.py +279 -0
- pyfect/stream/_zipping.py +213 -0
- pyfect/subscription_ref.py +66 -0
- pyfect/synchronized_ref.py +38 -0
- pyfect/take.py +36 -0
- pyfect/terminal/__init__.py +41 -0
- pyfect/terminal/_keys.py +113 -0
- pyfect/terminal/_live.py +223 -0
- pyfect/terminal/_service.py +73 -0
- pyfect/testing/__init__.py +122 -0
- pyfect/testing/_assertions.py +92 -0
- pyfect/testing/_clock.py +309 -0
- pyfect/testing/_console.py +164 -0
- pyfect/testing/_crypto.py +44 -0
- pyfect/testing/_decorators.py +211 -0
- pyfect/testing/_flaky.py +34 -0
- pyfect/testing/_layers.py +153 -0
- pyfect/testing/_prop.py +255 -0
- pyfect/testing/_runners.py +84 -0
- pyfect/testing/_runtime.py +174 -0
- pyfect/testing/_schema.py +238 -0
- pyfect/testing/plugin.py +59 -0
- pyfect/tracer/__init__.py +107 -0
- pyfect/tracer/_model.py +176 -0
- pyfect/tracer/_native.py +172 -0
- pyfect/tracer/_refs.py +87 -0
- pyfect/tracer/_spans.py +269 -0
- pyfect/transaction.py +340 -0
- pyfect/trie.py +317 -0
- pyfect/tx_chunk.py +127 -0
- pyfect/tx_deferred.py +75 -0
- pyfect/tx_hash_map.py +265 -0
- pyfect/tx_hash_set.py +151 -0
- pyfect/tx_priority_queue.py +224 -0
- pyfect/tx_pubsub.py +194 -0
- pyfect/tx_queue/__init__.py +27 -0
- pyfect/tx_queue/_queue.py +359 -0
- pyfect/tx_queue/_views.py +60 -0
- pyfect/tx_reentrant_lock.py +201 -0
- pyfect/tx_ref.py +79 -0
- pyfect/tx_semaphore.py +136 -0
- pyfect/tx_subscription_ref.py +118 -0
- pyfect/workers/__init__.py +62 -0
- pyfect/workers/_frames.py +82 -0
- pyfect/workers/_mailbox.py +51 -0
- pyfect/workers/_port.py +106 -0
- pyfect/workers/_process.py +155 -0
- pyfect/workers/_thread.py +100 -0
- pyfect/workers/python_worker.py +133 -0
- pyfect/workers/python_worker_runner.py +112 -0
- pyfect/workers/transferable.py +103 -0
- pyfect/workers/worker.py +273 -0
- pyfect/workers/worker_error.py +86 -0
- pyfect/workers/worker_runner.py +85 -0
- pyfect/workflow/__init__.py +78 -0
- pyfect/workflow/_crypto.py +23 -0
- pyfect/workflow/_encoded.py +381 -0
- pyfect/workflow/_memory.py +424 -0
- pyfect/workflow/_result.py +43 -0
- pyfect/workflow/_services.py +241 -0
- pyfect/workflow/_suspension.py +153 -0
- pyfect/workflow/activity.py +427 -0
- pyfect/workflow/durable_clock.py +72 -0
- pyfect/workflow/durable_deferred.py +403 -0
- pyfect/workflow/durable_queue.py +259 -0
- pyfect/workflow/workflow.py +509 -0
- pyfect/workflow/workflow_engine.py +42 -0
- pyfect/workflow/workflow_proxy.py +161 -0
- pyfect/workflow/workflow_proxy_server.py +146 -0
- pyfect_dev-0.1.0.dist-info/METADATA +296 -0
- pyfect_dev-0.1.0.dist-info/RECORD +1265 -0
- pyfect_dev-0.1.0.dist-info/WHEEL +4 -0
- pyfect_dev-0.1.0.dist-info/entry_points.txt +3 -0
- pyfect_dev-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"""Named wait points of a workflow execution (Effect's
|
|
2
|
+
``effect/workflow/DurableDeferred``)::
|
|
3
|
+
|
|
4
|
+
approval = durable_deferred.make("approval", success=S.Boolean)
|
|
5
|
+
|
|
6
|
+
# in the workflow: suspend until someone completes it
|
|
7
|
+
approved = yield from approval.wait()
|
|
8
|
+
|
|
9
|
+
# anywhere else, with the token the workflow handed out
|
|
10
|
+
yield from approval.succeed(token=token, value=True)
|
|
11
|
+
|
|
12
|
+
A deferred's result is stored by the engine, encoded by its schemas, so a
|
|
13
|
+
replay of the workflow reads it instead of waiting. A token names the
|
|
14
|
+
workflow, the execution and the deferred, so code outside the workflow
|
|
15
|
+
can complete the right wait point.
|
|
16
|
+
"""
|
|
17
|
+
# pyright: reportPrivateUsage=false
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import base64
|
|
22
|
+
from collections.abc import Generator, Mapping, Sequence
|
|
23
|
+
from typing import TYPE_CHECKING, Any, Never, NewType, cast
|
|
24
|
+
from weakref import WeakKeyDictionary
|
|
25
|
+
|
|
26
|
+
from pyfect import _json_text
|
|
27
|
+
from pyfect._concurrency import race_all as race_all_effects
|
|
28
|
+
from pyfect._core import Effect
|
|
29
|
+
from pyfect._effect_args import require_effect
|
|
30
|
+
from pyfect._js import text_encode
|
|
31
|
+
from pyfect.brand import Brand
|
|
32
|
+
from pyfect.cause import Cause, Interrupt, from_reasons
|
|
33
|
+
from pyfect.effect import from_exit, gen, provide_service, succeed, sync
|
|
34
|
+
from pyfect.exit import Exit, Failure, Success
|
|
35
|
+
from pyfect.schema import (
|
|
36
|
+
Class,
|
|
37
|
+
Codec,
|
|
38
|
+
Defect,
|
|
39
|
+
ExitSchema,
|
|
40
|
+
String,
|
|
41
|
+
StringFromBase64Url,
|
|
42
|
+
Tuple,
|
|
43
|
+
decode_sync,
|
|
44
|
+
decode_to,
|
|
45
|
+
encode_sync,
|
|
46
|
+
field,
|
|
47
|
+
from_brand,
|
|
48
|
+
from_json_string,
|
|
49
|
+
to_codec_json,
|
|
50
|
+
)
|
|
51
|
+
from pyfect.schema import Exit as ExitOf
|
|
52
|
+
from pyfect.schema import Never as NeverSchema
|
|
53
|
+
from pyfect.schema import Null as NullSchema
|
|
54
|
+
from pyfect.schema.transformation import transform
|
|
55
|
+
from pyfect.workflow._services import (
|
|
56
|
+
WorkflowEngine,
|
|
57
|
+
WorkflowEngineService,
|
|
58
|
+
WorkflowInstance,
|
|
59
|
+
WorkflowInstanceService,
|
|
60
|
+
)
|
|
61
|
+
from pyfect.workflow._suspension import suspend as suspend_run
|
|
62
|
+
from pyfect.workflow._suspension import wrap_activity_result
|
|
63
|
+
from pyfect.workflow.activity import CurrentAttempt, Exactly, codec_of
|
|
64
|
+
|
|
65
|
+
if TYPE_CHECKING:
|
|
66
|
+
from pyfect.workflow.workflow import Workflow
|
|
67
|
+
|
|
68
|
+
Token = NewType("Token", str)
|
|
69
|
+
"""A string naming a workflow, an execution and a deferred."""
|
|
70
|
+
|
|
71
|
+
TokenSchema = String.pipe(from_brand(Brand[str, Token].nominal()))
|
|
72
|
+
"""Tokens (Effect's ``DurableDeferred.Token``)."""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class TokenParsed(Class):
|
|
76
|
+
"""A token taken apart."""
|
|
77
|
+
|
|
78
|
+
workflow_name: str = field(String, key="workflowName")
|
|
79
|
+
execution_id: str = field(String, key="executionId")
|
|
80
|
+
deferred_name: str = field(String, key="deferredName")
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def as_token(self) -> Token:
|
|
84
|
+
"""The token: base64url of the JSON ``[workflow, execution,
|
|
85
|
+
deferred]``, as Effect writes it."""
|
|
86
|
+
text = _json_text.stringify([self.workflow_name, self.execution_id, self.deferred_name])
|
|
87
|
+
return Token(base64.urlsafe_b64encode(text_encode(text)).decode().rstrip("="))
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def from_string(token: str) -> TokenParsed:
|
|
91
|
+
"""Parse ``token``; raises ``SchemaError`` if it is not one."""
|
|
92
|
+
return decode_sync(TOKEN_FROM_STRING)(token)
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def encode(parsed: TokenParsed) -> str:
|
|
96
|
+
return encode_sync(TOKEN_FROM_STRING)(parsed)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _to_fields(parts: tuple[str, str, str]) -> Mapping[str, object]:
|
|
100
|
+
return {"workflowName": parts[0], "executionId": parts[1], "deferredName": parts[2]}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _from_fields(fields: Mapping[str, object]) -> tuple[str, str, str]:
|
|
104
|
+
return (
|
|
105
|
+
cast("str", fields["workflowName"]),
|
|
106
|
+
cast("str", fields["executionId"]),
|
|
107
|
+
cast("str", fields["deferredName"]),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
TOKEN_FROM_STRING = StringFromBase64Url.pipe(
|
|
112
|
+
decode_to(from_json_string(Tuple(String, String, String)))
|
|
113
|
+
).pipe(decode_to(TokenParsed, transform(decode=_to_fields, encode=_from_fields)))
|
|
114
|
+
"""Tokens as strings, decoded to ``TokenParsed`` (Effect's
|
|
115
|
+
``TokenParsed.FromString``)."""
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class DurableDeferred[A, E = Never]:
|
|
119
|
+
"""A named wait point of a workflow execution whose result is an ``A``
|
|
120
|
+
or an ``E`` failure. Make one with ``make``."""
|
|
121
|
+
|
|
122
|
+
__slots__ = ("error_schema", "exit_schema", "name", "success_schema")
|
|
123
|
+
|
|
124
|
+
def __init__(
|
|
125
|
+
self,
|
|
126
|
+
name: str,
|
|
127
|
+
*,
|
|
128
|
+
success: Codec[A, Any, Never, Never],
|
|
129
|
+
error: Codec[E, Any, Never, Never],
|
|
130
|
+
) -> None:
|
|
131
|
+
self.name = name
|
|
132
|
+
self.success_schema = success
|
|
133
|
+
self.error_schema = error
|
|
134
|
+
self.exit_schema: ExitSchema[A, E, Any, Any, Never, Never] = ExitOf(
|
|
135
|
+
to_codec_json(success), to_codec_json(error), to_codec_json(Defect())
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def __repr__(self) -> str:
|
|
139
|
+
return f"DurableDeferred({self.name!r})"
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def with_activity_attempt(self) -> Effect[DurableDeferred[A, E]]:
|
|
143
|
+
"""This deferred named for the current activity attempt
|
|
144
|
+
(``"{name}/{attempt}"``)."""
|
|
145
|
+
return CurrentAttempt().map(
|
|
146
|
+
lambda attempt: DurableDeferred(
|
|
147
|
+
f"{self.name}/{attempt}", success=self.success_schema, error=self.error_schema
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def wait(self) -> Effect[A, E, WorkflowEngine | WorkflowInstance]:
|
|
152
|
+
"""The deferred's result, suspending the execution until it has one
|
|
153
|
+
(Effect's ``DurableDeferred.await``)."""
|
|
154
|
+
return await_(self)
|
|
155
|
+
|
|
156
|
+
def into[R](
|
|
157
|
+
self, effect: Effect[A, E, R]
|
|
158
|
+
) -> Effect[A, E, R | WorkflowEngine | WorkflowInstance]:
|
|
159
|
+
"""Run ``effect`` and record its exit as this deferred's result,
|
|
160
|
+
waking executions waiting on it.
|
|
161
|
+
|
|
162
|
+
``effect`` and the write count as one in-flight activity of the
|
|
163
|
+
workflow, so siblings parked on durable waits do not suspend the
|
|
164
|
+
run until the exit is recorded. If ``effect`` parks on a durable
|
|
165
|
+
wait itself, the run suspends once the other activities finish and
|
|
166
|
+
nothing is recorded. Completing a deferred the run waits on still
|
|
167
|
+
replays the run."""
|
|
168
|
+
require_effect(effect, "effect")
|
|
169
|
+
return _record_held(effect, self, check_cache=False)
|
|
170
|
+
|
|
171
|
+
def token(self) -> Effect[Token, Never, WorkflowInstance]:
|
|
172
|
+
"""The token of this deferred in the current execution."""
|
|
173
|
+
return WorkflowInstance().map(
|
|
174
|
+
lambda instance: (
|
|
175
|
+
TokenParsed(
|
|
176
|
+
workflow_name=instance.workflow.tag,
|
|
177
|
+
execution_id=instance.execution_id,
|
|
178
|
+
deferred_name=self.name,
|
|
179
|
+
).as_token
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def token_from_execution_id(
|
|
184
|
+
self, *, workflow: Workflow[Any, Any, Any], execution_id: str
|
|
185
|
+
) -> Token:
|
|
186
|
+
return TokenParsed(
|
|
187
|
+
workflow_name=workflow.tag, execution_id=execution_id, deferred_name=self.name
|
|
188
|
+
).as_token
|
|
189
|
+
|
|
190
|
+
def token_from_payload[P](
|
|
191
|
+
self, *, workflow: Workflow[P, Any, Any], payload: P
|
|
192
|
+
) -> Effect[Token]:
|
|
193
|
+
return workflow.execution_id(payload).map(
|
|
194
|
+
lambda execution_id: self.token_from_execution_id(
|
|
195
|
+
workflow=workflow, execution_id=execution_id
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def done(self, *, token: str, exit: Exit[A, E]) -> Effect[None, Never, WorkflowEngine]:
|
|
200
|
+
"""Record ``exit`` as the result of the deferred ``token`` names,
|
|
201
|
+
waking its execution. A token naming another deferred is a defect
|
|
202
|
+
(Effect writes ``exit`` into that deferred, with this one's schema)."""
|
|
203
|
+
|
|
204
|
+
def body() -> Generator[Effect[Any, Never, Any], Any]:
|
|
205
|
+
engine = yield from WorkflowEngine()
|
|
206
|
+
parsed = TokenParsed.from_string(token)
|
|
207
|
+
if parsed.deferred_name != self.name:
|
|
208
|
+
raise ValueError(
|
|
209
|
+
f"token names the deferred {parsed.deferred_name!r}, not {self.name!r}"
|
|
210
|
+
)
|
|
211
|
+
yield from engine.deferred_done(
|
|
212
|
+
self,
|
|
213
|
+
workflow_name=parsed.workflow_name,
|
|
214
|
+
execution_id=parsed.execution_id,
|
|
215
|
+
deferred_name=parsed.deferred_name,
|
|
216
|
+
exit=exit,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
return gen(body)
|
|
220
|
+
|
|
221
|
+
def succeed(self, *, token: str, value: A) -> Effect[None, Never, WorkflowEngine]:
|
|
222
|
+
return self.done(token=token, exit=Success(value))
|
|
223
|
+
|
|
224
|
+
def fail(self, *, token: str, error: E) -> Effect[None, Never, WorkflowEngine]:
|
|
225
|
+
from pyfect.cause import fail as fail_cause_of
|
|
226
|
+
|
|
227
|
+
return self.done(token=token, exit=Failure(fail_cause_of(error)))
|
|
228
|
+
|
|
229
|
+
def fail_cause(self, *, token: str, cause: Cause[E]) -> Effect[None, Never, WorkflowEngine]:
|
|
230
|
+
return self.done(token=token, exit=Failure(cause))
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def make[A = None, E = Never](
|
|
234
|
+
name: str,
|
|
235
|
+
*,
|
|
236
|
+
success: Codec[A, Any, Never, Never] | None = None,
|
|
237
|
+
error: Codec[E, Any, Never, Never] | None = None,
|
|
238
|
+
) -> DurableDeferred[A, E]:
|
|
239
|
+
"""A deferred named ``name`` (unique within its workflow). ``success``
|
|
240
|
+
defaults to ``None`` (Effect's ``Void``), ``error`` to none."""
|
|
241
|
+
return DurableDeferred(
|
|
242
|
+
name,
|
|
243
|
+
success=cast("Codec[A, Any, Never, Never]", NullSchema) if success is None else success,
|
|
244
|
+
error=cast("Codec[E, Any, Never, Never]", NeverSchema) if error is None else error,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# --- waits and the runs that own them ---------------------------------------------------
|
|
249
|
+
|
|
250
|
+
_await_owners: WeakKeyDictionary[WorkflowInstanceService, list[str]] = WeakKeyDictionary()
|
|
251
|
+
"""The waits registered under the instance copy of a ``_record_held`` body."""
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _register_await(instance: WorkflowInstanceService, name: str) -> None:
|
|
255
|
+
instance.awaited_deferreds[name] = instance.awaited_deferreds.get(name, 0) + 1
|
|
256
|
+
owned = _await_owners.get(instance)
|
|
257
|
+
if owned is not None:
|
|
258
|
+
owned.append(name)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _release_awaits(instance: WorkflowInstanceService, names: list[str]) -> None:
|
|
262
|
+
for name in names:
|
|
263
|
+
count = instance.awaited_deferreds[name] - 1
|
|
264
|
+
if count == 0:
|
|
265
|
+
del instance.awaited_deferreds[name]
|
|
266
|
+
else:
|
|
267
|
+
instance.awaited_deferreds[name] = count
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def await_[A, E](self: DurableDeferred[A, E]) -> Effect[A, E, WorkflowEngine | WorkflowInstance]:
|
|
271
|
+
"""``self``'s result, suspending the execution until there is one."""
|
|
272
|
+
|
|
273
|
+
def body() -> Generator[Effect[Any, Any, Any], Any, A]:
|
|
274
|
+
engine = yield from WorkflowEngine()
|
|
275
|
+
instance = yield from WorkflowInstance()
|
|
276
|
+
# Register before the read, so any later completion can preempt the run.
|
|
277
|
+
_register_await(instance, self.name)
|
|
278
|
+
found = yield from wrap_activity_result(engine.deferred_result(self), lambda e: e is None)
|
|
279
|
+
if found is None:
|
|
280
|
+
return (yield from suspend_run(instance))
|
|
281
|
+
return (yield from from_exit(found))
|
|
282
|
+
|
|
283
|
+
return gen(body)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _record_exit(
|
|
287
|
+
engine: WorkflowEngineService,
|
|
288
|
+
instance: WorkflowInstanceService,
|
|
289
|
+
self: DurableDeferred[Any, Any],
|
|
290
|
+
ex: Exit[Any, Any],
|
|
291
|
+
) -> Effect[None]:
|
|
292
|
+
"""Record ``ex`` into ``self``. An exit of interrupts only is no result
|
|
293
|
+
(the effect suspended, was preempted or interrupted); interrupts mixed
|
|
294
|
+
into a failure are dropped from the recorded cause."""
|
|
295
|
+
if isinstance(ex, Failure):
|
|
296
|
+
if ex.cause.has_interrupts_only:
|
|
297
|
+
return succeed(None)
|
|
298
|
+
if ex.cause.has_interrupts:
|
|
299
|
+
ex = Failure(from_reasons(r for r in ex.cause.reasons if not isinstance(r, Interrupt)))
|
|
300
|
+
return engine.deferred_done(
|
|
301
|
+
self,
|
|
302
|
+
workflow_name=instance.workflow.tag,
|
|
303
|
+
execution_id=instance.execution_id,
|
|
304
|
+
deferred_name=self.name,
|
|
305
|
+
exit=ex,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _record_held[A, E, R](
|
|
310
|
+
effect: Effect[A, E, R], self: DurableDeferred[Any, Any], *, check_cache: bool
|
|
311
|
+
) -> Effect[A, E, R | WorkflowEngine | WorkflowInstance]:
|
|
312
|
+
"""Run ``effect`` as one activity of the workflow and record its exit
|
|
313
|
+
into ``self`` (with ``check_cache``, a recorded exit is returned
|
|
314
|
+
instead). The hold spans the lookup, ``effect`` and the write, so a
|
|
315
|
+
parked sibling cannot suspend the run before the continuation starts."""
|
|
316
|
+
|
|
317
|
+
def body() -> Generator[Effect[Any, Any, Any], Any, A]:
|
|
318
|
+
engine = yield from WorkflowEngine()
|
|
319
|
+
instance = yield from WorkflowInstance()
|
|
320
|
+
|
|
321
|
+
def held() -> Generator[Effect[Any, Any, Any], Any, Exit[A, E] | None]:
|
|
322
|
+
if check_cache:
|
|
323
|
+
cached = yield from engine.deferred_result(self)
|
|
324
|
+
if cached is not None:
|
|
325
|
+
return cached
|
|
326
|
+
# ``effect`` counts on its own activity state, so a durable wait
|
|
327
|
+
# inside it waits for its own siblings, not for this hold. The
|
|
328
|
+
# copy starts unsuspended: a parked arm of an enclosing race
|
|
329
|
+
# leaves that copy marked suspended.
|
|
330
|
+
local = instance.copy()
|
|
331
|
+
# A losing branch parked on a durable wait has exited when the
|
|
332
|
+
# race settles, so this body owns the waits registered beneath it.
|
|
333
|
+
owned: list[str] = []
|
|
334
|
+
_await_owners[local] = owned
|
|
335
|
+
# A parked ``effect`` interrupts the fiber it runs on; the child
|
|
336
|
+
# keeps that from skipping the suspension handling below.
|
|
337
|
+
fiber = yield from (
|
|
338
|
+
effect.pipe(provide_service(WorkflowInstance, local))
|
|
339
|
+
.on_exit(lambda ex: _record_exit(engine, instance, self, ex))
|
|
340
|
+
.fork(start_immediately=True)
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
def settle(ex: Exit[A, E]) -> Exit[A, E] | None:
|
|
344
|
+
if local.suspended and isinstance(ex, Failure) and ex.cause.has_interrupts_only:
|
|
345
|
+
return None
|
|
346
|
+
return ex
|
|
347
|
+
|
|
348
|
+
def release(result: Exit[Exit[A, E] | None, Never]) -> Effect[None]:
|
|
349
|
+
def run() -> None:
|
|
350
|
+
if isinstance(result, Success) and result.value is not None:
|
|
351
|
+
_release_awaits(instance, owned)
|
|
352
|
+
else:
|
|
353
|
+
parent = _await_owners.get(instance)
|
|
354
|
+
if parent is not None:
|
|
355
|
+
parent.extend(owned)
|
|
356
|
+
|
|
357
|
+
return sync(run)
|
|
358
|
+
|
|
359
|
+
return (
|
|
360
|
+
yield from fiber.await_exit()
|
|
361
|
+
.on_interrupt(fiber.interrupt())
|
|
362
|
+
.map(settle)
|
|
363
|
+
.on_exit(release)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
# Every branch parked: release the hold and let the enclosing
|
|
367
|
+
# activities finish before suspending.
|
|
368
|
+
found = yield from wrap_activity_result(gen(held), lambda e: e is None)
|
|
369
|
+
if found is None:
|
|
370
|
+
return (yield from suspend_run(instance))
|
|
371
|
+
return (yield from from_exit(found))
|
|
372
|
+
|
|
373
|
+
return gen(body)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def race_all[A, E, R](
|
|
377
|
+
*,
|
|
378
|
+
name: str,
|
|
379
|
+
success: Exactly[A],
|
|
380
|
+
error: Exactly[E],
|
|
381
|
+
effects: Sequence[Effect[A, E, R]],
|
|
382
|
+
) -> Effect[A, E, R | WorkflowEngine | WorkflowInstance]:
|
|
383
|
+
"""Race ``effects`` durably: the first to succeed wins (or the last
|
|
384
|
+
failure), its exit is recorded, and a replay gets the recorded exit
|
|
385
|
+
without running them again."""
|
|
386
|
+
if not effects:
|
|
387
|
+
raise ValueError("race_all needs at least one effect")
|
|
388
|
+
deferred: DurableDeferred[A, E] = DurableDeferred(
|
|
389
|
+
f"raceAll/{name}", success=codec_of(success), error=codec_of(error)
|
|
390
|
+
)
|
|
391
|
+
return _record_held(race_all_effects(*effects), deferred, check_cache=True)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
__all__ = [
|
|
395
|
+
"TOKEN_FROM_STRING",
|
|
396
|
+
"DurableDeferred",
|
|
397
|
+
"Token",
|
|
398
|
+
"TokenParsed",
|
|
399
|
+
"TokenSchema",
|
|
400
|
+
"await_",
|
|
401
|
+
"make",
|
|
402
|
+
"race_all",
|
|
403
|
+
]
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""Work a workflow hands to persisted background workers (Effect's
|
|
2
|
+
``effect/workflow/DurableQueue``)::
|
|
3
|
+
|
|
4
|
+
Emails = durable_queue.make(
|
|
5
|
+
name="emails",
|
|
6
|
+
payload=S.Struct(Email),
|
|
7
|
+
idempotency_key=lambda email: email["id"],
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
# in a workflow: offer the item and suspend until a worker is done
|
|
11
|
+
yield from Emails.process({"id": "welcome-ada", "to": "ada@example.com"})
|
|
12
|
+
|
|
13
|
+
# in a worker process
|
|
14
|
+
EmailWorker = Emails.worker(send_email, concurrency=5)
|
|
15
|
+
|
|
16
|
+
``process`` offers the item to the ``PersistedQueue`` named
|
|
17
|
+
``DurableQueue/<name>`` with a ``DurableDeferred`` token, then suspends;
|
|
18
|
+
a worker takes it, runs the handler and records the handler's ``Exit``
|
|
19
|
+
through the token, which resumes the workflow with the typed success or
|
|
20
|
+
error.
|
|
21
|
+
|
|
22
|
+
Delivery is at-least-once: a crash between the handler's success and the
|
|
23
|
+
acknowledgement delivers the item again, so handlers must be idempotent.
|
|
24
|
+
An item out of attempts is dead-lettered: its deferred never resolves and
|
|
25
|
+
the workflow stays parked until the item is requeued out of band (the
|
|
26
|
+
id-based de-duplication keeps replays from resurrecting it). Run
|
|
27
|
+
``persisted_queue.layer_cleanup`` in one instance to prune old items.
|
|
28
|
+
|
|
29
|
+
A worker killed while it holds an item stalls that item until its lock
|
|
30
|
+
expires: 120 seconds by default with ``persisted_queue.layer_store_sql`` and
|
|
31
|
+
90 with ``layer_store_redis``. Set it with ``lock_expiration=`` on the store.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
from collections.abc import Callable, Generator
|
|
37
|
+
from typing import Any, Never, TypedDict, cast
|
|
38
|
+
|
|
39
|
+
from pyfect._core import Effect
|
|
40
|
+
from pyfect.effect import die, fail, gen, never
|
|
41
|
+
from pyfect.exit import Exit
|
|
42
|
+
from pyfect.layer import Layer, effect_discard
|
|
43
|
+
from pyfect.logger import log_warning
|
|
44
|
+
from pyfect.persistence import persisted_queue
|
|
45
|
+
from pyfect.persistence.persisted_queue import PersistedQueueError, PersistedQueueFactory
|
|
46
|
+
from pyfect.schedule import Schedule, exponential, min, spaced
|
|
47
|
+
from pyfect.schema import Bottom, Codec, SchemaError, Struct
|
|
48
|
+
from pyfect.scope import Scope
|
|
49
|
+
from pyfect.tracer import ExternalSpan, Span, use_span
|
|
50
|
+
from pyfect.workflow._services import WorkflowEngine, WorkflowInstance
|
|
51
|
+
from pyfect.workflow.activity import idempotency_key as activity_idempotency_key
|
|
52
|
+
from pyfect.workflow.durable_deferred import DurableDeferred, Token, TokenParsed, TokenSchema
|
|
53
|
+
from pyfect.workflow.durable_deferred import make as make_deferred
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class _QueueItem(TypedDict):
|
|
57
|
+
token: Token
|
|
58
|
+
traceId: str
|
|
59
|
+
spanId: str
|
|
60
|
+
sampled: bool
|
|
61
|
+
payload: object
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
"""What the queue stores: the item and the token and span to answer to."""
|
|
65
|
+
|
|
66
|
+
_queue_schemas: dict[int, tuple[object, Bottom[_QueueItem, Any, Never, Never]]] = {}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _queue_schema(payload: Codec[Any, Any, Never, Never]) -> Bottom[_QueueItem, Any, Never, Never]:
|
|
70
|
+
cached = _queue_schemas.get(id(payload))
|
|
71
|
+
if cached is not None and cached[0] is payload:
|
|
72
|
+
return cached[1]
|
|
73
|
+
schema = Struct(_QueueItem, token=TokenSchema, payload=payload)
|
|
74
|
+
_queue_schemas[id(payload)] = (payload, schema)
|
|
75
|
+
return schema
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
DEFAULT_RETRY_SCHEDULE: Schedule[Any, Any, Any, Any] = min(exponential(0.5, 1.5), spaced(60))
|
|
79
|
+
"""How ``process`` retries a failed offer: exponential from 500 ms, at most
|
|
80
|
+
a minute apart."""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class DurableQueue[P, A = None, E = Never]:
|
|
84
|
+
"""A queue of ``P`` items whose workers succeed with ``A`` or fail with
|
|
85
|
+
``E``. Make one with ``make``."""
|
|
86
|
+
|
|
87
|
+
__slots__ = ("deferred", "idempotency_key", "name", "payload_schema")
|
|
88
|
+
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
*,
|
|
92
|
+
name: str,
|
|
93
|
+
payload_schema: Codec[P, Any, Never, Never],
|
|
94
|
+
idempotency_key: Callable[[P], str],
|
|
95
|
+
deferred: DurableDeferred[A, E],
|
|
96
|
+
) -> None:
|
|
97
|
+
self.name = name
|
|
98
|
+
self.payload_schema = payload_schema
|
|
99
|
+
self.idempotency_key = idempotency_key
|
|
100
|
+
self.deferred = deferred
|
|
101
|
+
|
|
102
|
+
def __repr__(self) -> str:
|
|
103
|
+
return f"DurableQueue({self.name!r})"
|
|
104
|
+
|
|
105
|
+
def _item_deferred(self, name: str) -> DurableDeferred[A, E]:
|
|
106
|
+
"""The deferred one queued item answers through."""
|
|
107
|
+
return DurableDeferred(
|
|
108
|
+
name, success=self.deferred.success_schema, error=self.deferred.error_schema
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def _item_done(self, token: Token, exit: Exit[A, E]) -> Effect[None, Never, WorkflowEngine]:
|
|
112
|
+
"""Answer the item ``token`` names, through its own deferred (a
|
|
113
|
+
deferred refuses a token naming another one)."""
|
|
114
|
+
return self._item_deferred(TokenParsed.from_string(token).deferred_name).done(
|
|
115
|
+
token=token, exit=exit
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def process(
|
|
119
|
+
self, payload: P, *, retry_schedule: Schedule[Any, Any, Any, Any] | None = None
|
|
120
|
+
) -> Effect[A, E, WorkflowEngine | WorkflowInstance | PersistedQueueFactory]:
|
|
121
|
+
"""Offer ``payload`` and wait (suspended) for a worker to process
|
|
122
|
+
it. A failed offer is retried on ``retry_schedule`` (logged), then
|
|
123
|
+
is a defect."""
|
|
124
|
+
|
|
125
|
+
def body() -> Generator[Effect[Any, Any, Any], Any, A]:
|
|
126
|
+
made = self.payload_schema.__codec__().make(payload)
|
|
127
|
+
queue_name = f"DurableQueue/{self.name}"
|
|
128
|
+
queue = yield from persisted_queue.make(
|
|
129
|
+
name=queue_name, schema=_queue_schema(self.payload_schema)
|
|
130
|
+
)
|
|
131
|
+
item_id = yield from activity_idempotency_key(
|
|
132
|
+
f"{queue_name}/{self.idempotency_key(made)}"
|
|
133
|
+
)
|
|
134
|
+
deferred = self._item_deferred(f"{self.deferred.name}/{item_id}")
|
|
135
|
+
token = yield from deferred.token()
|
|
136
|
+
|
|
137
|
+
def offer(span: Span) -> Effect[None]:
|
|
138
|
+
item: _QueueItem = {
|
|
139
|
+
"token": token,
|
|
140
|
+
"payload": made,
|
|
141
|
+
"traceId": span.trace_id,
|
|
142
|
+
"spanId": span.span_id,
|
|
143
|
+
"sampled": span.sampled,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
def schema_defect(
|
|
147
|
+
error: PersistedQueueError | SchemaError,
|
|
148
|
+
) -> Effect[Never, PersistedQueueError]:
|
|
149
|
+
return die(error) if isinstance(error, SchemaError) else fail(error)
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
queue.offer(item, id=item_id)
|
|
153
|
+
.tap_cause(log_warning)
|
|
154
|
+
.catch_all(schema_defect)
|
|
155
|
+
.retry(DEFAULT_RETRY_SCHEDULE if retry_schedule is None else retry_schedule)
|
|
156
|
+
.or_die()
|
|
157
|
+
.as_void()
|
|
158
|
+
.annotate_logs(
|
|
159
|
+
{
|
|
160
|
+
"package": "effect",
|
|
161
|
+
"module": "DurableQueue",
|
|
162
|
+
"fiber": "process",
|
|
163
|
+
"queueName": self.name,
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
yield from use_span(
|
|
169
|
+
f"DurableQueue/{self.name}/process", offer, attributes={"id": item_id}
|
|
170
|
+
)
|
|
171
|
+
return (yield from deferred.wait())
|
|
172
|
+
|
|
173
|
+
return gen(body)
|
|
174
|
+
|
|
175
|
+
def make_worker[R = Never](
|
|
176
|
+
self,
|
|
177
|
+
f: Callable[[P], Effect[A, E, R]],
|
|
178
|
+
*,
|
|
179
|
+
concurrency: int = 1,
|
|
180
|
+
) -> Effect[Never, Never, WorkflowEngine | PersistedQueueFactory | R]:
|
|
181
|
+
"""Take items forever, ``concurrency`` at a time, recording each
|
|
182
|
+
handler's exit for the workflow that offered it. A failure is
|
|
183
|
+
logged and the worker goes on."""
|
|
184
|
+
|
|
185
|
+
def body() -> Generator[Effect[Any, Any, Any], Any, Any]:
|
|
186
|
+
queue = yield from persisted_queue.make(
|
|
187
|
+
name=f"DurableQueue/{self.name}", schema=_queue_schema(self.payload_schema)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
def handle(
|
|
191
|
+
item: _QueueItem, _meta: persisted_queue.Metadata
|
|
192
|
+
) -> Effect[None, Never, Any]:
|
|
193
|
+
payload = cast("P", item["payload"])
|
|
194
|
+
return (
|
|
195
|
+
f(payload)
|
|
196
|
+
.exit()
|
|
197
|
+
.flat_map(lambda ex: self._item_done(item["token"], ex))
|
|
198
|
+
.with_span(
|
|
199
|
+
f"DurableQueue/{self.name}/worker",
|
|
200
|
+
capture_stack_trace=False,
|
|
201
|
+
parent=ExternalSpan(
|
|
202
|
+
trace_id=item["traceId"],
|
|
203
|
+
span_id=item["spanId"],
|
|
204
|
+
sampled=item["sampled"],
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
worker = (
|
|
210
|
+
queue.take(handle)
|
|
211
|
+
.catch_cause(log_warning)
|
|
212
|
+
.forever()
|
|
213
|
+
.annotate_logs(
|
|
214
|
+
{
|
|
215
|
+
"package": "effect",
|
|
216
|
+
"module": "DurableQueue",
|
|
217
|
+
"fiber": "worker",
|
|
218
|
+
"queueName": self.name,
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
yield from worker.replicate_effect(concurrency, concurrency=concurrency, discard=True)
|
|
223
|
+
return (yield from never)
|
|
224
|
+
|
|
225
|
+
return gen(body)
|
|
226
|
+
|
|
227
|
+
def worker[R = Never](
|
|
228
|
+
self, f: Callable[[P], Effect[A, E, R]], *, concurrency: int = 1
|
|
229
|
+
) -> Layer[Never, Never, WorkflowEngine | PersistedQueueFactory | R]:
|
|
230
|
+
"""A layer running ``make_worker`` in the background."""
|
|
231
|
+
started: Effect[object, Never, Any] = self.make_worker(
|
|
232
|
+
f, concurrency=concurrency
|
|
233
|
+
).fork_scoped()
|
|
234
|
+
return cast(
|
|
235
|
+
"Layer[Never, Never, WorkflowEngine | PersistedQueueFactory | R]",
|
|
236
|
+
effect_discard(cast("Effect[object, Never, Scope]", started)),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def make[P, A = None, E = Never](
|
|
241
|
+
*,
|
|
242
|
+
name: str,
|
|
243
|
+
payload: Codec[P, Any, Never, Never],
|
|
244
|
+
idempotency_key: Callable[[P], str],
|
|
245
|
+
success: Codec[A, Any, Never, Never] | None = None,
|
|
246
|
+
error: Codec[E, Any, Never, Never] | None = None,
|
|
247
|
+
) -> DurableQueue[P, A, E]:
|
|
248
|
+
"""A queue named ``name`` of ``payload`` items; ``idempotency_key``
|
|
249
|
+
names an item within its workflow execution. ``success`` defaults to
|
|
250
|
+
``None``, ``error`` to none."""
|
|
251
|
+
return DurableQueue(
|
|
252
|
+
name=name,
|
|
253
|
+
payload_schema=payload,
|
|
254
|
+
idempotency_key=idempotency_key,
|
|
255
|
+
deferred=make_deferred(f"DurableQueue/{name}", success=success, error=error),
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
__all__ = ["DEFAULT_RETRY_SCHEDULE", "DurableQueue", "make"]
|