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
pyfect/_fiber.py
ADDED
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
"""The fiber runtime.
|
|
2
|
+
|
|
3
|
+
A fiber runs one effect. It keeps an explicit stack of continuation frames
|
|
4
|
+
(so deep chains never hit Python's recursion limit) and runs on a
|
|
5
|
+
``Scheduler``. It never blocks: when it has to wait, it registers a resume
|
|
6
|
+
callback and parks (``SUSPEND``).
|
|
7
|
+
|
|
8
|
+
Resumes follow Effect's ``evaluate``: a resume, an interrupt or a start
|
|
9
|
+
called on the thread running the fiber's run runs the fiber at once,
|
|
10
|
+
nested in the caller, up to its next wait or yield, before the call
|
|
11
|
+
returns. A fork with ``start_immediately`` runs the child before the fork
|
|
12
|
+
returns. Nesting is bounded (``_MAX_NESTED_RUNS``): deeper than that, the
|
|
13
|
+
fiber runs right after the innermost running fiber's run instead, so a
|
|
14
|
+
chain of wake-ups cannot overflow the stack. Calls from other threads go
|
|
15
|
+
through the fiber's scheduler.
|
|
16
|
+
|
|
17
|
+
Interruption follows Effect's rules:
|
|
18
|
+
|
|
19
|
+
- Interrupting a fiber records an ``Interrupt`` at once when the caller is
|
|
20
|
+
on the thread of the fiber's run (otherwise via the scheduler). If the
|
|
21
|
+
fiber is interruptible and not running, it is unwound right away: a fiber
|
|
22
|
+
that has not started never runs its body, a parked fiber has its wait
|
|
23
|
+
cancelled (running its canceler), and a fiber queued after a yield or a
|
|
24
|
+
step-budget pause is unwound instead of resuming. A running fiber,
|
|
25
|
+
including one that interrupts itself, stops at its next step. A fiber
|
|
26
|
+
whose resume another thread already queued gets the interrupt after that
|
|
27
|
+
resumed run, since in Effect the resume would already have run.
|
|
28
|
+
- While interruptible and interrupted, the loop turns whatever comes next
|
|
29
|
+
into a failure, so the stack unwinds. Recovery handlers are skipped;
|
|
30
|
+
finalizers run, because they run in uninterruptible regions. The exit a
|
|
31
|
+
step returns reaches its continuation with no pause in between, so a
|
|
32
|
+
result the step decided (a taken item, an applied update) is never
|
|
33
|
+
dropped by an interrupt landing at a yield.
|
|
34
|
+
- Inside an uninterruptible region the interrupt waits until the region
|
|
35
|
+
ends.
|
|
36
|
+
|
|
37
|
+
Supervision: a fiber's children (from ``fork``) are interrupted and awaited
|
|
38
|
+
before the fiber completes.
|
|
39
|
+
"""
|
|
40
|
+
# pyright: reportPrivateUsage=false
|
|
41
|
+
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
import contextvars
|
|
45
|
+
import itertools
|
|
46
|
+
import math
|
|
47
|
+
import threading
|
|
48
|
+
from collections import deque
|
|
49
|
+
from collections.abc import Callable, Iterable
|
|
50
|
+
from dataclasses import dataclass
|
|
51
|
+
from functools import partial
|
|
52
|
+
from typing import TYPE_CHECKING, Any, Generic, Literal, Never, TypeVar
|
|
53
|
+
|
|
54
|
+
from pyfect._core import (
|
|
55
|
+
SUSPEND,
|
|
56
|
+
Effect,
|
|
57
|
+
Frame,
|
|
58
|
+
Register,
|
|
59
|
+
Step,
|
|
60
|
+
_Callback,
|
|
61
|
+
_FromExit,
|
|
62
|
+
_RestoreInterruptible,
|
|
63
|
+
_Succeed,
|
|
64
|
+
_Sync,
|
|
65
|
+
_WithFiber,
|
|
66
|
+
)
|
|
67
|
+
from pyfect._interrupt_reason import closed
|
|
68
|
+
from pyfect._metric_ref import FiberRuntimeMetrics
|
|
69
|
+
from pyfect._stack_frame import CurrentStackFrame, InterruptorStackTrace, StackFrame, StackTrace
|
|
70
|
+
from pyfect._tracer_ref import CurrentTracer
|
|
71
|
+
from pyfect._wake_tracking import Tracking
|
|
72
|
+
from pyfect.cause import Cause
|
|
73
|
+
from pyfect.cause import interrupt as interrupt_cause
|
|
74
|
+
from pyfect.context import Context, Reference, Tag
|
|
75
|
+
from pyfect.exit import Exit, Failure, Success
|
|
76
|
+
from pyfect.exit import die as exit_die
|
|
77
|
+
from pyfect.scheduler import (
|
|
78
|
+
BoundScheduler,
|
|
79
|
+
CurrentScheduler,
|
|
80
|
+
EffectScheduler,
|
|
81
|
+
MaxOpsBeforeYield,
|
|
82
|
+
PreventSchedulerYield,
|
|
83
|
+
Scheduler,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if TYPE_CHECKING:
|
|
87
|
+
from pyfect._metric_ref import FiberRuntimeMetricsService
|
|
88
|
+
from pyfect.scope import CloseableScope
|
|
89
|
+
from pyfect.tracer import ContextHook
|
|
90
|
+
|
|
91
|
+
A_co = TypeVar("A_co", covariant=True)
|
|
92
|
+
E_co = TypeVar("E_co", covariant=True, default=Never)
|
|
93
|
+
|
|
94
|
+
# Effect's ``maxInlineSteps``: how many continuations run back to back
|
|
95
|
+
# before the loop may yield.
|
|
96
|
+
_INLINE_STEPS = 32
|
|
97
|
+
|
|
98
|
+
# How many fiber runs may nest on one thread (a resume running the woken
|
|
99
|
+
# fiber inside the resumer's step, and so on). Past it, a run waits for the
|
|
100
|
+
# innermost one to finish, so the Python stack stays bounded.
|
|
101
|
+
_MAX_NESTED_RUNS = 16
|
|
102
|
+
|
|
103
|
+
_fiber_ids = itertools.count(1)
|
|
104
|
+
_resume_lock = threading.Lock()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
type _Run = tuple[Fiber[Any, Any], Step]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class _Drain:
|
|
111
|
+
"""Fiber runs of one run scheduler on this thread, nested ``depth``
|
|
112
|
+
runs deep: the fiber running now, then ``queue``, the runs that came in
|
|
113
|
+
while it ran but could not nest any deeper. ``first`` collects the runs
|
|
114
|
+
the current run hands over that way (children started immediately,
|
|
115
|
+
then the rest of the forking fiber), which go before everything
|
|
116
|
+
queued."""
|
|
117
|
+
|
|
118
|
+
__slots__ = ("current", "depth", "first", "queue", "run_scheduler")
|
|
119
|
+
|
|
120
|
+
def __init__(self, run_scheduler: Scheduler, depth: int) -> None:
|
|
121
|
+
self.run_scheduler = run_scheduler
|
|
122
|
+
self.depth = depth
|
|
123
|
+
self.current: Fiber[Any, Any] | None = None
|
|
124
|
+
# Made on first use: most drains run one fiber once.
|
|
125
|
+
self.queue: deque[_Run] | None = None
|
|
126
|
+
self.first: list[_Run] | None = None
|
|
127
|
+
|
|
128
|
+
def push(self, run: _Run) -> None:
|
|
129
|
+
if self.queue is None:
|
|
130
|
+
self.queue = deque()
|
|
131
|
+
self.queue.append(run)
|
|
132
|
+
|
|
133
|
+
def push_first(self, run: _Run) -> None:
|
|
134
|
+
if self.first is None:
|
|
135
|
+
self.first = []
|
|
136
|
+
self.first.append(run)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class _ThreadState(threading.local):
|
|
140
|
+
"""The drain running fibers on this thread, if any. It also knows the
|
|
141
|
+
fiber running now, so a fiber switch costs no thread-local writes."""
|
|
142
|
+
|
|
143
|
+
drain: _Drain | None = None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
_thread = _ThreadState()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def current_fiber() -> Fiber[Any, Any] | None:
|
|
150
|
+
"""The fiber running on this thread, if any."""
|
|
151
|
+
drain = _thread.drain
|
|
152
|
+
return None if drain is None else drain.current
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def current_run_scheduler() -> Scheduler | None:
|
|
156
|
+
"""The scheduler of the run whose fiber is running on this thread."""
|
|
157
|
+
drain = _thread.drain
|
|
158
|
+
return None if drain is None or drain.current is None else drain.run_scheduler
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
type Uninterruptible = bool | Literal["inherit"]
|
|
162
|
+
"""A forked fiber's starting interruptibility: ``False`` (interruptible),
|
|
163
|
+
``True``, or ``"inherit"`` the forking fiber's."""
|
|
164
|
+
|
|
165
|
+
type _Phase = Literal["pending", "running", "idle"]
|
|
166
|
+
"""``pending``: forked, first run not yet started. ``running``: inside
|
|
167
|
+
``_run``. ``idle``: parked in a wait, or queued to run again."""
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class _Wait:
|
|
171
|
+
"""One suspension. ``resume`` is safe to call from any thread, any
|
|
172
|
+
number of times; only the first call counts. ``done``, ``registering``
|
|
173
|
+
and ``sync_result`` change together under ``_resume_lock``: a resume
|
|
174
|
+
racing the end of registration on another thread (free-threaded
|
|
175
|
+
builds have no GIL to order them) either lands in ``sync_result``
|
|
176
|
+
before registration reads it, or sees registration over and resumes
|
|
177
|
+
the parked fiber.
|
|
178
|
+
|
|
179
|
+
The wait lets go of its fiber once claimed: ``resume`` is handed to the
|
|
180
|
+
outside (a future's callbacks, a listener list), which may keep it
|
|
181
|
+
after it fired, and must not keep the fiber, and its result, alive."""
|
|
182
|
+
|
|
183
|
+
__slots__ = ("canceler", "done", "fiber", "registering", "sync_result")
|
|
184
|
+
|
|
185
|
+
def __init__(self, fiber: Fiber[Any, Any]) -> None:
|
|
186
|
+
self.fiber: Fiber[Any, Any] | None = fiber
|
|
187
|
+
self.done = False
|
|
188
|
+
self.registering = True
|
|
189
|
+
self.sync_result: Effect[Any, Any, Any] | None = None
|
|
190
|
+
self.canceler: Effect[Any, Any, Any] | None = None
|
|
191
|
+
|
|
192
|
+
def claim(self) -> bool:
|
|
193
|
+
with _resume_lock:
|
|
194
|
+
if self.done:
|
|
195
|
+
return False
|
|
196
|
+
self.done = True
|
|
197
|
+
self.fiber = None
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
def end_registration(self) -> Effect[Any, Any, Any] | None:
|
|
201
|
+
"""Registration is over; the effect a resume during it left."""
|
|
202
|
+
with _resume_lock:
|
|
203
|
+
self.registering = False
|
|
204
|
+
result, self.sync_result = self.sync_result, None
|
|
205
|
+
return result
|
|
206
|
+
|
|
207
|
+
def resume(self, effect: Effect[Any, Any, Any]) -> None:
|
|
208
|
+
with _resume_lock:
|
|
209
|
+
fiber = self.fiber
|
|
210
|
+
if fiber is None:
|
|
211
|
+
return
|
|
212
|
+
self.done = True
|
|
213
|
+
self.fiber = None
|
|
214
|
+
self.canceler = None
|
|
215
|
+
if self.registering:
|
|
216
|
+
self.sync_result = effect
|
|
217
|
+
return
|
|
218
|
+
if fiber._on_run_thread():
|
|
219
|
+
fiber._resume(self, effect)
|
|
220
|
+
else:
|
|
221
|
+
fiber._scheduler.schedule(lambda: fiber._resume(self, effect))
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class _Yielded:
|
|
225
|
+
"""A run queued on the scheduler after a yield or a step-budget pause.
|
|
226
|
+
An interrupt takes it over (``Fiber._deliver_interrupt``); the queued
|
|
227
|
+
task then finds it gone and does nothing."""
|
|
228
|
+
|
|
229
|
+
__slots__ = ("step",)
|
|
230
|
+
|
|
231
|
+
def __init__(self, step: Step) -> None:
|
|
232
|
+
self.step = step
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class _ReturnExit(Frame):
|
|
236
|
+
"""Finish with a stored exit, ignoring whatever ran above it."""
|
|
237
|
+
|
|
238
|
+
__slots__ = ("exit",)
|
|
239
|
+
|
|
240
|
+
def __init__(self, ex: Exit[Any, Any]) -> None:
|
|
241
|
+
self.exit = ex
|
|
242
|
+
|
|
243
|
+
def _on_success(self, value: Any, fiber: Fiber[Any, Any]) -> Step:
|
|
244
|
+
return self.exit
|
|
245
|
+
|
|
246
|
+
def _on_failure(self, cause: Cause[Any], fiber: Fiber[Any, Any]) -> Step:
|
|
247
|
+
return self.exit
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class Fiber(Generic[A_co, E_co]):
|
|
251
|
+
"""A running effect. Obtain one with ``eff.fork()``."""
|
|
252
|
+
|
|
253
|
+
__slots__ = (
|
|
254
|
+
"__weakref__",
|
|
255
|
+
"_async_context",
|
|
256
|
+
"_children",
|
|
257
|
+
"_ctx",
|
|
258
|
+
"_exit",
|
|
259
|
+
"_interrupt",
|
|
260
|
+
"_interruptible",
|
|
261
|
+
"_interrupts_after_resume",
|
|
262
|
+
"_observers",
|
|
263
|
+
"_ops_budget",
|
|
264
|
+
"_parent",
|
|
265
|
+
"_phase",
|
|
266
|
+
"_run_scheduler",
|
|
267
|
+
"_runtime_metrics",
|
|
268
|
+
"_scheduler",
|
|
269
|
+
"_should_yield",
|
|
270
|
+
"_stack",
|
|
271
|
+
"_stack_frame",
|
|
272
|
+
"_tracer_hook",
|
|
273
|
+
"_waiting",
|
|
274
|
+
"_yielded",
|
|
275
|
+
"current_op_count",
|
|
276
|
+
"id",
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
def __init__(
|
|
280
|
+
self,
|
|
281
|
+
run_scheduler: Scheduler,
|
|
282
|
+
context: Context,
|
|
283
|
+
parent: Fiber[Any, Any] | None,
|
|
284
|
+
*,
|
|
285
|
+
interruptible: bool = True,
|
|
286
|
+
) -> None:
|
|
287
|
+
"""A fiber of the run driven by ``run_scheduler``. It runs on
|
|
288
|
+
``context``'s ``CurrentScheduler``."""
|
|
289
|
+
self.id = next(_fiber_ids)
|
|
290
|
+
self._run_scheduler = run_scheduler
|
|
291
|
+
self.current_op_count = 0
|
|
292
|
+
self._parent = parent
|
|
293
|
+
self._stack: list[Frame] = []
|
|
294
|
+
self._interruptible = interruptible
|
|
295
|
+
self._interrupt: Cause[Never] | None = None
|
|
296
|
+
# Interrupts that arrived while a resume was queued (see _resume).
|
|
297
|
+
self._interrupts_after_resume: list[tuple[int | None, Context | None]] = []
|
|
298
|
+
# A dict, not a set: children are interrupted in fork order, as in Effect.
|
|
299
|
+
self._children: dict[Fiber[Any, Any], None] = {}
|
|
300
|
+
self._observers: list[Callable[[Exit[Any, Any]], None]] = []
|
|
301
|
+
self._exit: Exit[Any, Any] | None = None
|
|
302
|
+
self._waiting: _Wait | None = None
|
|
303
|
+
self._yielded: _Yielded | None = None
|
|
304
|
+
self._phase: _Phase = "pending"
|
|
305
|
+
# The ``contextvars`` context the fiber parked or paused in, which
|
|
306
|
+
# it resumes in (Effect's captured async context).
|
|
307
|
+
self._async_context: contextvars.Context | None = None
|
|
308
|
+
self._set_context(context)
|
|
309
|
+
if self._runtime_metrics is not None:
|
|
310
|
+
self._runtime_metrics.record_fiber_start(self._ctx)
|
|
311
|
+
|
|
312
|
+
def __repr__(self) -> str:
|
|
313
|
+
return f"Fiber(#{self.id})"
|
|
314
|
+
|
|
315
|
+
# --- public API (all effects, except the ``unsafe_`` reads) ------------
|
|
316
|
+
|
|
317
|
+
def await_exit(self) -> Effect[Exit[A_co, E_co], Never, Never]:
|
|
318
|
+
"""Wait for the fiber and succeed with its ``Exit``."""
|
|
319
|
+
|
|
320
|
+
def register(resume: Callable[[Effect[Any, Any, Any]], None]) -> Effect[None] | None:
|
|
321
|
+
if self._exit is not None:
|
|
322
|
+
resume(_Succeed(self._exit))
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
def observer(ex: Exit[Any, Any]) -> None:
|
|
326
|
+
resume(_Succeed(ex))
|
|
327
|
+
|
|
328
|
+
self._observers.append(observer)
|
|
329
|
+
if self._exit is not None:
|
|
330
|
+
# It finished on another thread (a fiber of another run)
|
|
331
|
+
# between the check and the append; resume counts once.
|
|
332
|
+
resume(_Succeed(self._exit))
|
|
333
|
+
return None
|
|
334
|
+
return _Sync(lambda: self._remove_observer(observer))
|
|
335
|
+
|
|
336
|
+
return _Callback(register)
|
|
337
|
+
|
|
338
|
+
def join(self) -> Effect[A_co, E_co, Never]:
|
|
339
|
+
"""Wait for the fiber and take on its result: its value, or its
|
|
340
|
+
failure."""
|
|
341
|
+
return self.await_exit().flat_map(_FromExit)
|
|
342
|
+
|
|
343
|
+
def interrupt(self) -> Effect[None, Never, Never]:
|
|
344
|
+
"""Interrupt the fiber and wait until it has finished cleaning up.
|
|
345
|
+
Read the outcome with ``await_exit``."""
|
|
346
|
+
return _WithFiber(lambda me: self._interrupt_from(me, me.id))
|
|
347
|
+
|
|
348
|
+
def interrupt_as(self, by: int | None) -> Effect[None, Never, Never]:
|
|
349
|
+
"""``interrupt``, attributed to the fiber with id ``by``."""
|
|
350
|
+
return _WithFiber(lambda me: self._interrupt_from(me, by))
|
|
351
|
+
|
|
352
|
+
def _interrupt_from(self, me: Fiber[Any, Any], by: int | None) -> Effect[None, Never, Never]:
|
|
353
|
+
self.interrupt_as_unsafe(by, me._interruptor_annotations())
|
|
354
|
+
return self.await_exit().as_void()
|
|
355
|
+
|
|
356
|
+
def run_in(self, scope: CloseableScope) -> Fiber[A_co, E_co]:
|
|
357
|
+
"""Tie the fiber to ``scope``: closing it interrupts the fiber, and
|
|
358
|
+
the fiber leaves the scope when it exits. Registering again moves
|
|
359
|
+
the fiber's finalizer to the end, so it runs first. A fiber in an
|
|
360
|
+
already closed scope is interrupted. Call on the scheduler thread."""
|
|
361
|
+
if self._exit is not None:
|
|
362
|
+
return self
|
|
363
|
+
if scope._closed is not None:
|
|
364
|
+
self.interrupt_as_unsafe(self.id, closed("Scope"))
|
|
365
|
+
else:
|
|
366
|
+
scope._watch(self)
|
|
367
|
+
return self
|
|
368
|
+
|
|
369
|
+
def get_ref[S](self, reference: type[Reference[S]]) -> S:
|
|
370
|
+
"""The value of ``reference`` in this fiber's current context (its
|
|
371
|
+
default when nobody provided one). Loggers read the log annotations
|
|
372
|
+
and spans with it."""
|
|
373
|
+
return self._ctx.get_unsafe(reference)
|
|
374
|
+
|
|
375
|
+
@staticmethod
|
|
376
|
+
def get_current() -> Fiber[Any, Any] | None:
|
|
377
|
+
"""The fiber running on this thread, if any."""
|
|
378
|
+
return current_fiber()
|
|
379
|
+
|
|
380
|
+
@staticmethod
|
|
381
|
+
def await_all[A, E](fibers: Iterable[Fiber[A, E]]) -> Effect[list[Exit[A, E]], Never, Never]:
|
|
382
|
+
"""Wait for every fiber; succeed with their exits in order."""
|
|
383
|
+
from pyfect._fiber_all import await_all
|
|
384
|
+
|
|
385
|
+
return await_all(fibers)
|
|
386
|
+
|
|
387
|
+
@staticmethod
|
|
388
|
+
def join_all[A, E](fibers: Iterable[Fiber[A, E]]) -> Effect[list[A], E, Never]:
|
|
389
|
+
"""Wait for every fiber and succeed with their values in order, or
|
|
390
|
+
fail with the first failure (the others keep running)."""
|
|
391
|
+
from pyfect._fiber_all import join_all
|
|
392
|
+
|
|
393
|
+
return join_all(fibers)
|
|
394
|
+
|
|
395
|
+
@staticmethod
|
|
396
|
+
def interrupt_all(fibers: Iterable[Fiber[Any, Any]]) -> Effect[None, Never, Never]:
|
|
397
|
+
"""Interrupt every fiber and wait for all of them to finish."""
|
|
398
|
+
from pyfect._fiber_all import interrupt_all
|
|
399
|
+
|
|
400
|
+
return interrupt_all(fibers)
|
|
401
|
+
|
|
402
|
+
@staticmethod
|
|
403
|
+
def interrupt_all_as(
|
|
404
|
+
fibers: Iterable[Fiber[Any, Any]], by: int | None
|
|
405
|
+
) -> Effect[None, Never, Never]:
|
|
406
|
+
"""``interrupt_all``, attributed to the fiber with id ``by``."""
|
|
407
|
+
from pyfect._fiber_all import interrupt_all_as
|
|
408
|
+
|
|
409
|
+
return interrupt_all_as(fibers, by)
|
|
410
|
+
|
|
411
|
+
def poll_unsafe(self) -> Exit[A_co, E_co] | None:
|
|
412
|
+
"""The exit if the fiber is done. Only meaningful on the fiber's
|
|
413
|
+
scheduler thread."""
|
|
414
|
+
return self._exit
|
|
415
|
+
|
|
416
|
+
def interrupt_as_unsafe(self, by: int | None, annotations: Context | None = None) -> None:
|
|
417
|
+
"""Ask the fiber to stop. Safe from any thread; does not wait.
|
|
418
|
+
|
|
419
|
+
From the thread running the fiber's run (a fiber of it, or code
|
|
420
|
+
outside any fiber there) it takes effect at once (as Effect's
|
|
421
|
+
``interruptUnsafe``); from other threads it goes through the
|
|
422
|
+
scheduler. The interruption carries ``annotations`` (the
|
|
423
|
+
interrupting fiber's ``InterruptorStackTrace``) and the frame this
|
|
424
|
+
fiber is in (``StackTrace``)."""
|
|
425
|
+
if self._on_run_thread():
|
|
426
|
+
self._deliver_interrupt(by, annotations)
|
|
427
|
+
else:
|
|
428
|
+
self._scheduler.schedule(lambda: self._deliver_interrupt(by, annotations))
|
|
429
|
+
|
|
430
|
+
def add_observer_unsafe(
|
|
431
|
+
self, observer: Callable[[Exit[A_co, E_co]], None]
|
|
432
|
+
) -> Callable[[], None]:
|
|
433
|
+
"""Call ``observer`` with the exit once the fiber is done (at once if
|
|
434
|
+
it already is). Returns a function that removes the observer. Must
|
|
435
|
+
be called on the scheduler thread."""
|
|
436
|
+
if self._exit is not None:
|
|
437
|
+
observer(self._exit)
|
|
438
|
+
return _no_op
|
|
439
|
+
self._observers.append(observer)
|
|
440
|
+
return lambda: self._remove_observer(observer)
|
|
441
|
+
|
|
442
|
+
# --- runtime internals --------------------------------------------------
|
|
443
|
+
|
|
444
|
+
@property
|
|
445
|
+
def _context(self) -> Context:
|
|
446
|
+
return self._ctx
|
|
447
|
+
|
|
448
|
+
def _set_context(self, context: Context) -> None:
|
|
449
|
+
"""Switch context, and with it the scheduler and step budget read
|
|
450
|
+
from it (Effect's fiber context cache)."""
|
|
451
|
+
view = _last_view[0]
|
|
452
|
+
if (
|
|
453
|
+
view is None
|
|
454
|
+
or view.given is not context
|
|
455
|
+
or view.run_scheduler is not self._run_scheduler
|
|
456
|
+
):
|
|
457
|
+
view = _last_view[0] = _RunView.of(context, self._run_scheduler)
|
|
458
|
+
self._ctx = view.context
|
|
459
|
+
self._scheduler: Scheduler = view.scheduler
|
|
460
|
+
self._ops_budget: float = view.ops_budget
|
|
461
|
+
self._should_yield: EffectScheduler | None = view.should_yield
|
|
462
|
+
self._tracer_hook: ContextHook | None = view.tracer_hook
|
|
463
|
+
self._runtime_metrics: FiberRuntimeMetricsService | None = view.runtime_metrics
|
|
464
|
+
self._stack_frame: StackFrame | None = view.stack_frame
|
|
465
|
+
|
|
466
|
+
def _on_run_thread(self) -> bool:
|
|
467
|
+
"""Whether this thread runs this fiber's run now: one of its fibers
|
|
468
|
+
is running here, or its scheduler is running tasks here. Resumes
|
|
469
|
+
and interrupts from here run the fiber at once."""
|
|
470
|
+
run = self._run_scheduler
|
|
471
|
+
return current_run_scheduler() is run or (isinstance(run, Tracking) and run.runs_here())
|
|
472
|
+
|
|
473
|
+
def _interruptor_annotations(self) -> Context | None:
|
|
474
|
+
"""What an interruption this fiber sends records about it."""
|
|
475
|
+
frame = self._stack_frame
|
|
476
|
+
return None if frame is None else Context.make(InterruptorStackTrace, frame)
|
|
477
|
+
|
|
478
|
+
def _start(self, effect: Effect[Any, Any, Any]) -> None:
|
|
479
|
+
self._scheduler.schedule(lambda: self._drive(effect))
|
|
480
|
+
|
|
481
|
+
def _push(self, frame: Frame) -> None:
|
|
482
|
+
self._stack.append(frame)
|
|
483
|
+
|
|
484
|
+
def _interrupted_and_interruptible(self) -> bool:
|
|
485
|
+
return self._interrupt is not None and self._interruptible
|
|
486
|
+
|
|
487
|
+
def _run(self, start: Step) -> None:
|
|
488
|
+
if self._exit is not None:
|
|
489
|
+
return # finished early by an interrupt; a stale start or resume
|
|
490
|
+
self._phase = "running"
|
|
491
|
+
parked_in, self._async_context = self._async_context, None
|
|
492
|
+
try:
|
|
493
|
+
if parked_in is None:
|
|
494
|
+
self._loop(start)
|
|
495
|
+
else:
|
|
496
|
+
parked_in.run(self._loop, start)
|
|
497
|
+
finally:
|
|
498
|
+
self._phase = "idle"
|
|
499
|
+
if self._exit is None:
|
|
500
|
+
# Parked or paused: resume in this context, not the waker's.
|
|
501
|
+
self._async_context = contextvars.copy_context() if parked_in is None else parked_in
|
|
502
|
+
|
|
503
|
+
def _loop(self, start: Step) -> None:
|
|
504
|
+
cur: Step = start
|
|
505
|
+
ops = 0
|
|
506
|
+
# Whether ``cur`` is the exit a step (or a frame that ``claims``)
|
|
507
|
+
# just returned: a result it decided (a taken item, an applied
|
|
508
|
+
# update) reaches its continuation with no pause in between, where
|
|
509
|
+
# an interrupt could drop it.
|
|
510
|
+
decided = False
|
|
511
|
+
while True:
|
|
512
|
+
if self._interrupt is not None and self._interruptible:
|
|
513
|
+
cur = self._with_interrupt(cur, self._interrupt)
|
|
514
|
+
|
|
515
|
+
if isinstance(cur, Effect):
|
|
516
|
+
ops += 1
|
|
517
|
+
if ops > self._ops_budget and self._pause_due(cur, ops):
|
|
518
|
+
return
|
|
519
|
+
try:
|
|
520
|
+
hook = self._tracer_hook
|
|
521
|
+
cur = cur._step(self) if hook is None else _run_hooked(hook, cur, self)
|
|
522
|
+
except BaseException as exc:
|
|
523
|
+
cur = self._raised(exc)
|
|
524
|
+
decided = True
|
|
525
|
+
elif isinstance(cur, Success | Failure):
|
|
526
|
+
if not self._stack:
|
|
527
|
+
if self._stack_frame is not None and isinstance(cur, Failure):
|
|
528
|
+
cur = self._in_frame(cur, self._stack_frame)
|
|
529
|
+
nxt = self._finish(cur)
|
|
530
|
+
if nxt is None:
|
|
531
|
+
return
|
|
532
|
+
cur = nxt
|
|
533
|
+
continue
|
|
534
|
+
# A continuation counts as a step, but as in Effect a fiber
|
|
535
|
+
# may yield before one only every ``_INLINE_STEPS`` steps: a
|
|
536
|
+
# long chain of ``map``s still yields, while a primitive's
|
|
537
|
+
# result normally reaches its continuation in the same step.
|
|
538
|
+
ops += 1
|
|
539
|
+
if (
|
|
540
|
+
not decided
|
|
541
|
+
and ops % _INLINE_STEPS == 0
|
|
542
|
+
and ops > self._ops_budget
|
|
543
|
+
and self._pause_due(cur, ops)
|
|
544
|
+
):
|
|
545
|
+
return
|
|
546
|
+
frame = self._stack.pop()
|
|
547
|
+
decided = frame.claims
|
|
548
|
+
cur = self._continue(frame, cur)
|
|
549
|
+
elif cur is SUSPEND:
|
|
550
|
+
return # a resume callback will call _run again
|
|
551
|
+
else: # untyped user code handed back something that is not an effect
|
|
552
|
+
cur = exit_die(TypeError(f"not a valid effect: {cur!r}"))
|
|
553
|
+
|
|
554
|
+
def _pause_due(self, cur: Step, ops: int) -> bool:
|
|
555
|
+
"""Past the budget: pause before ``cur``, unless an
|
|
556
|
+
``EffectScheduler`` (asked before each step, as Effect) says not to. A raising
|
|
557
|
+
``should_yield`` is a defect, which the fiber goes on with after the
|
|
558
|
+
pause."""
|
|
559
|
+
check = self._should_yield
|
|
560
|
+
if check is not None:
|
|
561
|
+
self.current_op_count = ops
|
|
562
|
+
try:
|
|
563
|
+
if not check.should_yield(self):
|
|
564
|
+
return False
|
|
565
|
+
except BaseException as exc:
|
|
566
|
+
cur = self._raised(exc)
|
|
567
|
+
self._pause(cur)
|
|
568
|
+
return True
|
|
569
|
+
|
|
570
|
+
def _pause(self, cur: Step) -> None:
|
|
571
|
+
"""The step budget is spent: let other fibers run, then go on with
|
|
572
|
+
``cur``."""
|
|
573
|
+
self._queue_yielded(_Yielded(cur))
|
|
574
|
+
|
|
575
|
+
def _queue_yielded(self, yielded: _Yielded, priority: int = 0) -> None:
|
|
576
|
+
self._yielded = yielded
|
|
577
|
+
|
|
578
|
+
def go_on() -> None:
|
|
579
|
+
if self._yielded is yielded:
|
|
580
|
+
self._yielded = None
|
|
581
|
+
self._drive(yielded.step)
|
|
582
|
+
|
|
583
|
+
self._scheduler.schedule(go_on, priority)
|
|
584
|
+
|
|
585
|
+
def _continue(self, frame: Frame, ex: Exit[Any, Any]) -> Step:
|
|
586
|
+
hook = self._tracer_hook
|
|
587
|
+
try:
|
|
588
|
+
if isinstance(ex, Success):
|
|
589
|
+
if hook is None:
|
|
590
|
+
return frame._on_success(ex.value, self)
|
|
591
|
+
else:
|
|
592
|
+
# A failure meets its next handler (or the end of the fiber,
|
|
593
|
+
# in _loop) annotated with the frame it failed in, as
|
|
594
|
+
# Effect's failure exits are.
|
|
595
|
+
if self._stack_frame is not None:
|
|
596
|
+
ex = self._in_frame(ex, self._stack_frame)
|
|
597
|
+
if hook is None:
|
|
598
|
+
return frame._on_failure(ex.cause, self)
|
|
599
|
+
step: Callable[[], Step] = partial(_hand_over, frame, ex, self)
|
|
600
|
+
return hook(step, self)
|
|
601
|
+
except BaseException as exc:
|
|
602
|
+
return self._raised(exc)
|
|
603
|
+
|
|
604
|
+
def _raised(self, exc: BaseException) -> Step:
|
|
605
|
+
"""What the fiber goes on with when user code (a step or a
|
|
606
|
+
continuation) raised ``exc``: a defect, as Effect makes anything
|
|
607
|
+
thrown, whatever the exception's class.
|
|
608
|
+
|
|
609
|
+
Ctrl-C is not the program's own error: the step it cut short ends
|
|
610
|
+
as interrupted (the fiber goes on with that on its next turn), and
|
|
611
|
+
the ``KeyboardInterrupt`` goes on to the runner, which interrupts
|
|
612
|
+
the main fiber and waits for its finalizers, as when Ctrl-C lands
|
|
613
|
+
while every fiber is parked."""
|
|
614
|
+
if isinstance(exc, KeyboardInterrupt):
|
|
615
|
+
self._pause(Failure(self._record_interrupt(None, None)))
|
|
616
|
+
raise exc
|
|
617
|
+
return exit_die(exc)
|
|
618
|
+
|
|
619
|
+
@staticmethod
|
|
620
|
+
def _in_frame(failure: Failure[Any], frame: StackFrame) -> Failure[Any]:
|
|
621
|
+
"""Effect's failure annotation: reasons without a ``StackTrace`` get
|
|
622
|
+
the frame the fiber fails in."""
|
|
623
|
+
annotated = failure.cause.annotate(Context.make(StackTrace, frame))
|
|
624
|
+
return failure if annotated is failure.cause else Failure(annotated)
|
|
625
|
+
|
|
626
|
+
@staticmethod
|
|
627
|
+
def _with_interrupt(cur: Step, cause: Cause[Never]) -> Step:
|
|
628
|
+
if isinstance(cur, Failure):
|
|
629
|
+
with_interrupts = cur.cause.add_interrupts(cause)
|
|
630
|
+
return cur if with_interrupts is cur.cause else Failure(with_interrupts)
|
|
631
|
+
return Failure(cause)
|
|
632
|
+
|
|
633
|
+
def _finish(self, ex: Exit[Any, Any]) -> Step | None:
|
|
634
|
+
"""The stack is empty. Interrupt children first; then complete."""
|
|
635
|
+
if self._children:
|
|
636
|
+
children = list(self._children)
|
|
637
|
+
self._interruptible = False
|
|
638
|
+
self._push(_ReturnExit(ex))
|
|
639
|
+
return interrupt_all(children, self.id, self._interruptor_annotations())
|
|
640
|
+
self._exit = ex
|
|
641
|
+
if self._runtime_metrics is not None:
|
|
642
|
+
self._runtime_metrics.record_fiber_end(self._ctx, ex)
|
|
643
|
+
view = _last_view[0]
|
|
644
|
+
if view is not None and view.context is self._ctx:
|
|
645
|
+
parent = self._parent
|
|
646
|
+
if parent is None or parent._ctx is not self._ctx:
|
|
647
|
+
# Keep the context of a fiber that is done alive no longer
|
|
648
|
+
# than a fiber that still uses it (a closed scope's exit,
|
|
649
|
+
# say), as Effect, whose cache is per fiber.
|
|
650
|
+
_last_view[0] = None
|
|
651
|
+
if self._parent is not None:
|
|
652
|
+
self._parent._children.pop(self, None)
|
|
653
|
+
observers, self._observers = self._observers, []
|
|
654
|
+
for observer in observers:
|
|
655
|
+
# As Effect: a raise stops the observers after it.
|
|
656
|
+
observer(ex)
|
|
657
|
+
return None
|
|
658
|
+
|
|
659
|
+
def _remove_observer(self, observer: Callable[[Exit[Any, Any]], None]) -> None:
|
|
660
|
+
if self._exit is None and observer in self._observers:
|
|
661
|
+
self._observers.remove(observer)
|
|
662
|
+
|
|
663
|
+
def _fork(
|
|
664
|
+
self,
|
|
665
|
+
effect: Effect[Any, Any, Any],
|
|
666
|
+
*,
|
|
667
|
+
daemon: bool,
|
|
668
|
+
start_immediately: bool,
|
|
669
|
+
uninterruptible: Uninterruptible = False,
|
|
670
|
+
) -> Fiber[Any, Any]:
|
|
671
|
+
"""A fiber running ``effect``, a child of this one unless ``daemon``.
|
|
672
|
+
With ``start_immediately`` it runs now, up to its first wait or
|
|
673
|
+
yield, before this fiber goes on (Effect's ``runForkWith``, and
|
|
674
|
+
``fork*`` with ``startImmediately``); otherwise it is scheduled.
|
|
675
|
+
Required, so each port states which of the two Effect does."""
|
|
676
|
+
interruptible = self._interruptible if uninterruptible == "inherit" else not uninterruptible
|
|
677
|
+
child: Fiber[Any, Any] = Fiber(
|
|
678
|
+
self._run_scheduler, self._ctx, None if daemon else self, interruptible=interruptible
|
|
679
|
+
)
|
|
680
|
+
if not daemon:
|
|
681
|
+
self._children[child] = None
|
|
682
|
+
if start_immediately:
|
|
683
|
+
child._drive_first(effect)
|
|
684
|
+
else:
|
|
685
|
+
child._start(effect)
|
|
686
|
+
return child
|
|
687
|
+
|
|
688
|
+
def _after_started(self, then: Step) -> Step:
|
|
689
|
+
"""Go on with ``then`` once the fibers this run started immediately
|
|
690
|
+
have run (up to their first wait or yield)."""
|
|
691
|
+
drain = _thread.drain
|
|
692
|
+
if drain is None or not drain.first:
|
|
693
|
+
return then
|
|
694
|
+
drain.push_first((self, then))
|
|
695
|
+
return SUSPEND
|
|
696
|
+
|
|
697
|
+
def _yield_now(self, priority: int = 0) -> Step:
|
|
698
|
+
self._queue_yielded(_Yielded(Success(None)), priority)
|
|
699
|
+
return SUSPEND
|
|
700
|
+
|
|
701
|
+
def _suspend(self, register: Register[Any, Any, Any]) -> Step:
|
|
702
|
+
wait = _Wait(self)
|
|
703
|
+
try:
|
|
704
|
+
canceler = register(wait.resume)
|
|
705
|
+
except BaseException:
|
|
706
|
+
wait.claim()
|
|
707
|
+
raise
|
|
708
|
+
sync_result = wait.end_registration()
|
|
709
|
+
if sync_result is not None:
|
|
710
|
+
return sync_result
|
|
711
|
+
wait.canceler = canceler
|
|
712
|
+
self._waiting = wait
|
|
713
|
+
if self._interrupted_and_interruptible():
|
|
714
|
+
return self._cancel_wait(wait)
|
|
715
|
+
return SUSPEND
|
|
716
|
+
|
|
717
|
+
def _resume(self, wait: _Wait, effect: Effect[Any, Any, Any]) -> None:
|
|
718
|
+
"""Run a claimed resume now. In Effect a resume runs the fiber at
|
|
719
|
+
once, so an interrupt that arrived while another thread's resume
|
|
720
|
+
was queued lands only once the resumed run has finished, parked or
|
|
721
|
+
yielded."""
|
|
722
|
+
if self._waiting is wait:
|
|
723
|
+
self._waiting = None
|
|
724
|
+
self._evaluate(effect)
|
|
725
|
+
deferred, self._interrupts_after_resume = self._interrupts_after_resume, []
|
|
726
|
+
for by, interruptor in deferred:
|
|
727
|
+
self._deliver_interrupt(by, interruptor)
|
|
728
|
+
|
|
729
|
+
def _deliver_interrupt(self, by: int | None, annotations: Context | None) -> None:
|
|
730
|
+
if self._exit is not None:
|
|
731
|
+
return
|
|
732
|
+
wait = self._waiting
|
|
733
|
+
if wait is not None and self._interruptible and self._phase != "running":
|
|
734
|
+
if not wait.claim():
|
|
735
|
+
self._interrupts_after_resume.append((by, annotations))
|
|
736
|
+
return
|
|
737
|
+
self._record_interrupt(by, annotations)
|
|
738
|
+
step = self._abandon_wait(wait)
|
|
739
|
+
self._evaluate(step)
|
|
740
|
+
return
|
|
741
|
+
if wait is not None and wait.done:
|
|
742
|
+
self._interrupts_after_resume.append((by, annotations))
|
|
743
|
+
return
|
|
744
|
+
interrupt = self._record_interrupt(by, annotations)
|
|
745
|
+
if not self._interruptible or self._phase == "running":
|
|
746
|
+
return # the loop raises it at its next interruptible step
|
|
747
|
+
yielded = self._yielded
|
|
748
|
+
if yielded is not None:
|
|
749
|
+
# Queued after a yield or a pause: unwind now (Effect's
|
|
750
|
+
# evaluate of a yielded fiber); the queued task does nothing.
|
|
751
|
+
self._yielded = None
|
|
752
|
+
self._evaluate(yielded.step)
|
|
753
|
+
elif self._phase == "pending":
|
|
754
|
+
self._evaluate(Failure(interrupt))
|
|
755
|
+
# Otherwise it is queued to run on this thread, nested deeper than
|
|
756
|
+
# runs may go, and meets the interrupt at its first step.
|
|
757
|
+
|
|
758
|
+
def _record_interrupt(self, by: int | None, annotations: Context | None) -> Cause[Never]:
|
|
759
|
+
cause = interrupt_cause(by)
|
|
760
|
+
if self._stack_frame is not None:
|
|
761
|
+
cause = cause.annotate(Context.make(StackTrace, self._stack_frame))
|
|
762
|
+
if annotations is not None:
|
|
763
|
+
cause = cause.annotate(annotations)
|
|
764
|
+
if self._interrupt is not None:
|
|
765
|
+
cause = self._interrupt.add_interrupts(cause)
|
|
766
|
+
self._interrupt = cause
|
|
767
|
+
return cause
|
|
768
|
+
|
|
769
|
+
def _evaluate(self, step: Step) -> None:
|
|
770
|
+
"""Run this fiber with ``step`` now, nested in whatever runs on this
|
|
771
|
+
thread (Effect's ``evaluate``). Past ``_MAX_NESTED_RUNS`` nested
|
|
772
|
+
runs of this run it runs right after the innermost one instead."""
|
|
773
|
+
outer = _thread.drain
|
|
774
|
+
if outer is None:
|
|
775
|
+
self._drain((self, step), 0)
|
|
776
|
+
elif outer.depth >= _MAX_NESTED_RUNS and outer.run_scheduler is self._run_scheduler:
|
|
777
|
+
outer.push((self, step))
|
|
778
|
+
else:
|
|
779
|
+
self._drain((self, step), outer.depth + 1)
|
|
780
|
+
|
|
781
|
+
def _drive(self, step: Step) -> None:
|
|
782
|
+
"""Run this fiber from a scheduled task: now, or, if the scheduler
|
|
783
|
+
runs its tasks inside a fiber of the run, right after that fiber's
|
|
784
|
+
run."""
|
|
785
|
+
outer = _thread.drain
|
|
786
|
+
if outer is not None and outer.run_scheduler is self._run_scheduler:
|
|
787
|
+
outer.push((self, step))
|
|
788
|
+
else:
|
|
789
|
+
self._evaluate(step)
|
|
790
|
+
|
|
791
|
+
def _drain(self, start: _Run, depth: int) -> None:
|
|
792
|
+
outer = _thread.drain
|
|
793
|
+
_thread.drain = drain = _Drain(self._run_scheduler, depth)
|
|
794
|
+
fiber, nxt = start
|
|
795
|
+
try:
|
|
796
|
+
while True:
|
|
797
|
+
drain.current = fiber
|
|
798
|
+
fiber._run(nxt)
|
|
799
|
+
drain.current = None
|
|
800
|
+
first = drain.first
|
|
801
|
+
if first:
|
|
802
|
+
drain.first = None
|
|
803
|
+
if drain.queue is None:
|
|
804
|
+
drain.queue = deque(first)
|
|
805
|
+
else:
|
|
806
|
+
drain.queue.extendleft(reversed(first))
|
|
807
|
+
queue = drain.queue
|
|
808
|
+
if not queue:
|
|
809
|
+
break
|
|
810
|
+
fiber, nxt = queue.popleft()
|
|
811
|
+
finally:
|
|
812
|
+
_thread.drain = outer
|
|
813
|
+
# Only left over if a run raised (Ctrl-C): queue the rest, so no
|
|
814
|
+
# fiber is lost.
|
|
815
|
+
for fiber, nxt in [*(drain.first or ()), *(drain.queue or ())]:
|
|
816
|
+
fiber._scheduler.schedule(lambda f=fiber, n=nxt: f._drive(n))
|
|
817
|
+
|
|
818
|
+
def _drive_first(self, step: Step) -> None:
|
|
819
|
+
"""Start this fiber now, nested in the running one (Effect's
|
|
820
|
+
``startImmediately``); past ``_MAX_NESTED_RUNS`` nested runs, right
|
|
821
|
+
after the current run, before anything else queued."""
|
|
822
|
+
drain = _thread.drain
|
|
823
|
+
if (
|
|
824
|
+
drain is not None
|
|
825
|
+
and drain.run_scheduler is self._run_scheduler
|
|
826
|
+
and drain.depth >= _MAX_NESTED_RUNS
|
|
827
|
+
):
|
|
828
|
+
drain.push_first((self, step))
|
|
829
|
+
else:
|
|
830
|
+
self._evaluate(step)
|
|
831
|
+
|
|
832
|
+
def _cancel_wait(self, wait: _Wait) -> Step:
|
|
833
|
+
"""An interrupted fiber just parked: abandon the wait, unless another
|
|
834
|
+
thread resumed it meanwhile (that run then meets the interrupt at
|
|
835
|
+
its first step)."""
|
|
836
|
+
if not wait.claim():
|
|
837
|
+
return SUSPEND
|
|
838
|
+
return self._abandon_wait(wait)
|
|
839
|
+
|
|
840
|
+
def _abandon_wait(self, wait: _Wait) -> Step:
|
|
841
|
+
"""Give up a claimed wait because of an interrupt. Returns the step
|
|
842
|
+
to continue with (the canceler, then the interrupt)."""
|
|
843
|
+
self._waiting = None
|
|
844
|
+
assert self._interrupt is not None
|
|
845
|
+
if wait.canceler is None:
|
|
846
|
+
return Failure(self._interrupt)
|
|
847
|
+
# Run the canceler uninterruptibly; restoring interruptibility
|
|
848
|
+
# afterwards makes the loop raise the interrupt.
|
|
849
|
+
self._push(_RestoreInterruptible(True))
|
|
850
|
+
self._interruptible = False
|
|
851
|
+
return wait.canceler
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
def _no_op() -> None:
|
|
855
|
+
pass
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def _run_hooked(hook: ContextHook, cur: Effect[Any, Any, Any], fiber: Fiber[Any, Any]) -> Step:
|
|
859
|
+
step: Callable[[], Step] = partial(cur._step, fiber)
|
|
860
|
+
return hook(step, fiber)
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
def _hand_over(frame: Frame, ex: Exit[Any, Any], fiber: Fiber[Any, Any]) -> Step:
|
|
864
|
+
if isinstance(ex, Success):
|
|
865
|
+
return frame._on_success(ex.value, fiber)
|
|
866
|
+
return frame._on_failure(ex.cause, fiber)
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
class _RunScheduler(Tag[Scheduler]):
|
|
870
|
+
"""The scheduler of the run a context was made in."""
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
@dataclass(frozen=True, slots=True)
|
|
874
|
+
class _RunView:
|
|
875
|
+
"""What a fiber of the run on ``run_scheduler`` reads from ``given``.
|
|
876
|
+
Contexts are immutable, so the last one computed is kept for the next
|
|
877
|
+
fiber switching to the same context (every fork does)."""
|
|
878
|
+
|
|
879
|
+
given: Context
|
|
880
|
+
run_scheduler: Scheduler
|
|
881
|
+
context: Context
|
|
882
|
+
scheduler: Scheduler
|
|
883
|
+
ops_budget: float
|
|
884
|
+
should_yield: EffectScheduler | None
|
|
885
|
+
tracer_hook: ContextHook | None
|
|
886
|
+
runtime_metrics: FiberRuntimeMetricsService | None
|
|
887
|
+
stack_frame: StackFrame | None
|
|
888
|
+
|
|
889
|
+
@staticmethod
|
|
890
|
+
def of(given: Context, run_scheduler: Scheduler) -> _RunView:
|
|
891
|
+
context = given
|
|
892
|
+
if context.lookup(_RunScheduler)[1] is not run_scheduler:
|
|
893
|
+
context = _into_run(context, run_scheduler)
|
|
894
|
+
scheduler = context.lookup(CurrentScheduler)[1]
|
|
895
|
+
if isinstance(scheduler, BoundScheduler) and scheduler.run is not run_scheduler:
|
|
896
|
+
scheduler = scheduler.effect_scheduler
|
|
897
|
+
if isinstance(scheduler, EffectScheduler):
|
|
898
|
+
scheduler = BoundScheduler(scheduler, run_scheduler)
|
|
899
|
+
context = context.add(CurrentScheduler, scheduler)
|
|
900
|
+
should_yield = scheduler.effect_scheduler if isinstance(scheduler, BoundScheduler) else None
|
|
901
|
+
if context.lookup(PreventSchedulerYield)[1]:
|
|
902
|
+
budget = math.inf
|
|
903
|
+
should_yield = None
|
|
904
|
+
elif should_yield is not None:
|
|
905
|
+
budget = 0
|
|
906
|
+
else:
|
|
907
|
+
budget = max(1, context.lookup(MaxOpsBeforeYield)[1])
|
|
908
|
+
hook = context.lookup(CurrentTracer)[1].context
|
|
909
|
+
runtime_metrics = context.lookup(FiberRuntimeMetrics)[1]
|
|
910
|
+
frame = context.lookup(CurrentStackFrame)[1]
|
|
911
|
+
return _RunView(
|
|
912
|
+
given,
|
|
913
|
+
run_scheduler,
|
|
914
|
+
context,
|
|
915
|
+
scheduler,
|
|
916
|
+
budget,
|
|
917
|
+
should_yield,
|
|
918
|
+
hook,
|
|
919
|
+
runtime_metrics,
|
|
920
|
+
frame,
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
# One entry: the view the last fiber switch computed.
|
|
925
|
+
_last_view: list[_RunView | None] = [None]
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def _into_run(context: Context, run_scheduler: Scheduler) -> Context:
|
|
929
|
+
"""``context`` for a fiber of the run on ``run_scheduler``. A context
|
|
930
|
+
from another run (kept by a finalizer, say) keeps a scheduler it was
|
|
931
|
+
explicitly given, but not that run's own, which may no longer run."""
|
|
932
|
+
found, earlier = context.lookup(_RunScheduler)
|
|
933
|
+
in_run = context.add(_RunScheduler, run_scheduler)
|
|
934
|
+
if CurrentScheduler in context.tags():
|
|
935
|
+
given = context.lookup(CurrentScheduler)[1]
|
|
936
|
+
if not found or given is not earlier:
|
|
937
|
+
return in_run
|
|
938
|
+
return in_run.add(CurrentScheduler, run_scheduler)
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
def root_fiber(scheduler: Scheduler, context: Context) -> Fiber[Any, Any]:
|
|
942
|
+
"""A fiber with no parent, running on ``scheduler`` unless an effect
|
|
943
|
+
provides another ``CurrentScheduler``."""
|
|
944
|
+
return Fiber(scheduler, context, None)
|
|
945
|
+
|
|
946
|
+
|
|
947
|
+
def interrupt_all(
|
|
948
|
+
fibers: list[Fiber[Any, Any]], by: int | None, annotations: Context | None
|
|
949
|
+
) -> Effect[None, Never, Never]:
|
|
950
|
+
"""Interrupt every fiber and wait for all of them to finish."""
|
|
951
|
+
|
|
952
|
+
def register(resume: Callable[[Effect[Any, Any, Any]], None]) -> None:
|
|
953
|
+
remaining = len(fibers)
|
|
954
|
+
if remaining == 0:
|
|
955
|
+
resume(_Succeed(None))
|
|
956
|
+
return
|
|
957
|
+
|
|
958
|
+
def observer(_: Exit[Any, Any]) -> None:
|
|
959
|
+
nonlocal remaining
|
|
960
|
+
remaining -= 1
|
|
961
|
+
if remaining == 0:
|
|
962
|
+
resume(_Succeed(None))
|
|
963
|
+
|
|
964
|
+
for f in fibers:
|
|
965
|
+
f.interrupt_as_unsafe(by, annotations)
|
|
966
|
+
for f in fibers:
|
|
967
|
+
f.add_observer_unsafe(observer)
|
|
968
|
+
|
|
969
|
+
return _Callback(register)
|