pyfect-dev 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyfect/__init__.py +729 -0
- pyfect/_all.py +242 -0
- pyfect/_background.py +28 -0
- pyfect/_binary_codecs.py +124 -0
- pyfect/_cached.py +165 -0
- pyfect/_catch.py +128 -0
- pyfect/_catch_reason.py +574 -0
- pyfect/_concurrency.py +291 -0
- pyfect/_context_effects.py +163 -0
- pyfect/_context_managers.py +76 -0
- pyfect/_core.py +368 -0
- pyfect/_digits.py +71 -0
- pyfect/_effect_args.py +46 -0
- pyfect/_effect_base.py +71 -0
- pyfect/_effect_concurrency.py +133 -0
- pyfect/_effect_eager.py +103 -0
- pyfect/_effect_errors.py +344 -0
- pyfect/_effect_folding.py +110 -0
- pyfect/_effect_hash.py +140 -0
- pyfect/_effect_logging.py +57 -0
- pyfect/_effect_metrics.py +102 -0
- pyfect/_effect_resources.py +179 -0
- pyfect/_effect_services.py +25 -0
- pyfect/_effect_timing.py +199 -0
- pyfect/_effect_tracing.py +99 -0
- pyfect/_equal.py +195 -0
- pyfect/_extras.py +43 -0
- pyfect/_fiber.py +969 -0
- pyfect/_fiber_all.py +134 -0
- pyfect/_fiber_collections.py +106 -0
- pyfect/_fn.py +706 -0
- pyfect/_hamt.py +174 -0
- pyfect/_interrupt_reason.py +33 -0
- pyfect/_isaac.py +149 -0
- pyfect/_iterables.py +231 -0
- pyfect/_js.py +263 -0
- pyfect/_js_date.py +87 -0
- pyfect/_json_text.py +286 -0
- pyfect/_json_value.py +9 -0
- pyfect/_layer_core.py +403 -0
- pyfect/_layer_for_life.py +33 -0
- pyfect/_layer_tracing.py +70 -0
- pyfect/_locked_ref.py +147 -0
- pyfect/_loop_thread.py +152 -0
- pyfect/_metric_ref.py +29 -0
- pyfect/_one_or_many.py +14 -0
- pyfect/_os_wait.py +118 -0
- pyfect/_path_posix.py +129 -0
- pyfect/_primitives.py +434 -0
- pyfect/_public.py +36 -0
- pyfect/_reasons.py +64 -0
- pyfect/_released.py +67 -0
- pyfect/_root.py +87 -0
- pyfect/_round_robin.py +73 -0
- pyfect/_some.py +17 -0
- pyfect/_stack.py +52 -0
- pyfect/_stack_frame.py +114 -0
- pyfect/_string_hash.py +35 -0
- pyfect/_subscriber_queue.py +47 -0
- pyfect/_tags.py +14 -0
- pyfect/_tracer_ref.py +21 -0
- pyfect/_tx_ops.py +77 -0
- pyfect/_unsubscripted.py +23 -0
- pyfect/_url.py +54 -0
- pyfect/_waiters.py +127 -0
- pyfect/_wake_tracking.py +98 -0
- pyfect/_wire_codec.py +123 -0
- pyfect/ai/__init__.py +73 -0
- pyfect/ai/_approvals.py +249 -0
- pyfect/ai/_decision_answers.py +185 -0
- pyfect/ai/_generated_client.py +298 -0
- pyfect/ai/_generated_support.py +84 -0
- pyfect/ai/_mcp/__init__.py +1 -0
- pyfect/ai/_mcp/batch.py +68 -0
- pyfect/ai/_mcp/client_transport.py +412 -0
- pyfect/ai/_mcp/client_wire.py +284 -0
- pyfect/ai/_mcp/core.py +490 -0
- pyfect/ai/_mcp/dispatch.py +1048 -0
- pyfect/ai/_mcp/http.py +462 -0
- pyfect/ai/_mcp/http_admission.py +269 -0
- pyfect/ai/_mcp/http_status.py +27 -0
- pyfect/ai/_mcp/input_wire.py +226 -0
- pyfect/ai/_mcp/log_levels.py +55 -0
- pyfect/ai/_mcp/param_headers.py +63 -0
- pyfect/ai/_mcp/projection.py +248 -0
- pyfect/ai/_mcp/register.py +683 -0
- pyfect/ai/_mcp/reverse_policy.py +155 -0
- pyfect/ai/_mcp/routing_header.py +24 -0
- pyfect/ai/_mcp/sessions.py +114 -0
- pyfect/ai/_mcp/stdio.py +110 -0
- pyfect/ai/_mcp/subscriptions.py +134 -0
- pyfect/ai/_mcp/tool_schema.py +35 -0
- pyfect/ai/_noul_answers.py +64 -0
- pyfect/ai/_openai_all_of.py +137 -0
- pyfect/ai/_openai_errors.py +233 -0
- pyfect/ai/_provider.py +439 -0
- pyfect/ai/_provider_tools.py +129 -0
- pyfect/ai/_stream_resolution.py +231 -0
- pyfect/ai/_structured_output_checks.py +138 -0
- pyfect/ai/_structured_output_codec.py +229 -0
- pyfect/ai/_structured_output_json.py +109 -0
- pyfect/ai/_tool_call_ids.py +80 -0
- pyfect/ai/ai_error.py +785 -0
- pyfect/ai/anthropic/__init__.py +28 -0
- pyfect/ai/anthropic/_errors.py +164 -0
- pyfect/ai/anthropic/_prompt.py +375 -0
- pyfect/ai/anthropic/_response.py +443 -0
- pyfect/ai/anthropic/_schema.py +206 -0
- pyfect/ai/anthropic/_stream.py +377 -0
- pyfect/ai/anthropic/_tools.py +248 -0
- pyfect/ai/anthropic/client.py +289 -0
- pyfect/ai/anthropic/config.py +32 -0
- pyfect/ai/anthropic/error.py +27 -0
- pyfect/ai/anthropic/language_model.py +366 -0
- pyfect/ai/anthropic/telemetry.py +51 -0
- pyfect/ai/anthropic/tool.py +570 -0
- pyfect/ai/anthropic_structured_output.py +138 -0
- pyfect/ai/chat.py +548 -0
- pyfect/ai/cloudflare/__init__.py +15 -0
- pyfect/ai/cloudflare/_errors.py +108 -0
- pyfect/ai/cloudflare/client.py +174 -0
- pyfect/ai/cloudflare/decision_model.py +117 -0
- pyfect/ai/cloudflare/schema.py +144 -0
- pyfect/ai/decision.py +290 -0
- pyfect/ai/decision_model.py +283 -0
- pyfect/ai/embedding_model.py +177 -0
- pyfect/ai/id_generator.py +79 -0
- pyfect/ai/language_model.py +1077 -0
- pyfect/ai/mcp_client.py +682 -0
- pyfect/ai/mcp_client_error.py +223 -0
- pyfect/ai/mcp_protocol.py +205 -0
- pyfect/ai/mcp_schema.py +1300 -0
- pyfect/ai/mcp_server.py +404 -0
- pyfect/ai/model.py +56 -0
- pyfect/ai/openai/__init__.py +49 -0
- pyfect/ai/openai/_errors.py +89 -0
- pyfect/ai/openai/_prompt.py +527 -0
- pyfect/ai/openai/_requests.py +34 -0
- pyfect/ai/openai/_response.py +569 -0
- pyfect/ai/openai/_schema.py +769 -0
- pyfect/ai/openai/_stream.py +771 -0
- pyfect/ai/openai/_tools.py +128 -0
- pyfect/ai/openai/_web_socket.py +295 -0
- pyfect/ai/openai/client.py +296 -0
- pyfect/ai/openai/client_generated.py +123 -0
- pyfect/ai/openai/config.py +34 -0
- pyfect/ai/openai/decision_model.py +177 -0
- pyfect/ai/openai/embedding_model.py +116 -0
- pyfect/ai/openai/error.py +8 -0
- pyfect/ai/openai/generated/__init__.py +7213 -0
- pyfect/ai/openai/generated/_recursive.py +18 -0
- pyfect/ai/openai/generated/_schemas_0.py +1015 -0
- pyfect/ai/openai/generated/_schemas_1.py +1016 -0
- pyfect/ai/openai/generated/_schemas_2.py +1017 -0
- pyfect/ai/openai/generated/_schemas_3.py +615 -0
- pyfect/ai/openai/generated/client.py +5298 -0
- pyfect/ai/openai/generated/types.py +8123 -0
- pyfect/ai/openai/language_model.py +304 -0
- pyfect/ai/openai/telemetry.py +19 -0
- pyfect/ai/openai/tool.py +391 -0
- pyfect/ai/openai_compat/__init__.py +25 -0
- pyfect/ai/openai_compat/_prompt.py +191 -0
- pyfect/ai/openai_compat/_request.py +153 -0
- pyfect/ai/openai_compat/_response.py +407 -0
- pyfect/ai/openai_compat/_schema.py +97 -0
- pyfect/ai/openai_compat/client.py +234 -0
- pyfect/ai/openai_compat/config.py +32 -0
- pyfect/ai/openai_compat/embedding_model.py +139 -0
- pyfect/ai/openai_compat/error.py +7 -0
- pyfect/ai/openai_compat/language_model.py +245 -0
- pyfect/ai/openai_compat/telemetry.py +51 -0
- pyfect/ai/openai_structured_output.py +118 -0
- pyfect/ai/openrouter/__init__.py +36 -0
- pyfect/ai/openrouter/_errors.py +186 -0
- pyfect/ai/openrouter/_prompt.py +272 -0
- pyfect/ai/openrouter/_response.py +226 -0
- pyfect/ai/openrouter/_stream.py +320 -0
- pyfect/ai/openrouter/client.py +283 -0
- pyfect/ai/openrouter/config.py +36 -0
- pyfect/ai/openrouter/decision_model.py +161 -0
- pyfect/ai/openrouter/error.py +23 -0
- pyfect/ai/openrouter/generated/__init__.py +3029 -0
- pyfect/ai/openrouter/generated/_recursive.py +15 -0
- pyfect/ai/openrouter/generated/_schemas_0.py +1015 -0
- pyfect/ai/openrouter/generated/_schemas_1.py +522 -0
- pyfect/ai/openrouter/generated/client.py +1445 -0
- pyfect/ai/openrouter/generated/types.py +4769 -0
- pyfect/ai/openrouter/language_model.py +345 -0
- pyfect/ai/openrouter/schema.py +165 -0
- pyfect/ai/prompt.py +664 -0
- pyfect/ai/response.py +768 -0
- pyfect/ai/response_id_tracker.py +82 -0
- pyfect/ai/telemetry.py +126 -0
- pyfect/ai/tokenizer.py +61 -0
- pyfect/ai/tool.py +536 -0
- pyfect/ai/toolkit.py +490 -0
- pyfect/ai/typesafe/__init__.py +17 -0
- pyfect/ai/typesafe/_errors.py +120 -0
- pyfect/ai/typesafe/client.py +146 -0
- pyfect/ai/typesafe/config.py +34 -0
- pyfect/ai/typesafe/decision_model.py +90 -0
- pyfect/ai/typesafe/schema.py +140 -0
- pyfect/batching/__init__.py +58 -0
- pyfect/batching/_api.py +71 -0
- pyfect/batching/_engine.py +229 -0
- pyfect/batching/_from_effect.py +76 -0
- pyfect/batching/_persisted.py +98 -0
- pyfect/batching/_request.py +133 -0
- pyfect/batching/_resolver.py +477 -0
- pyfect/batching/_with_cache.py +90 -0
- pyfect/big_decimal.py +500 -0
- pyfect/brand.py +114 -0
- pyfect/byte_size.py +354 -0
- pyfect/cache/__init__.py +29 -0
- pyfect/cache/_cache.py +426 -0
- pyfect/cache/_expiry.py +69 -0
- pyfect/cache/_scoped_cache.py +546 -0
- pyfect/case.py +174 -0
- pyfect/cause.py +462 -0
- pyfect/channel/__init__.py +155 -0
- pyfect/channel/_base.py +92 -0
- pyfect/channel/_catch.py +158 -0
- pyfect/channel/_catch_reason.py +572 -0
- pyfect/channel/_channel.py +158 -0
- pyfect/channel/_combinators.py +220 -0
- pyfect/channel/_concurrency_methods.py +217 -0
- pyfect/channel/_concurrent_map.py +118 -0
- pyfect/channel/_constructors.py +349 -0
- pyfect/channel/_context.py +232 -0
- pyfect/channel/_error_methods.py +336 -0
- pyfect/channel/_errors.py +180 -0
- pyfect/channel/_filters.py +225 -0
- pyfect/channel/_lifecycle.py +71 -0
- pyfect/channel/_lifecycle_methods.py +206 -0
- pyfect/channel/_loop.py +100 -0
- pyfect/channel/_merge.py +246 -0
- pyfect/channel/_repeat.py +154 -0
- pyfect/channel/_run.py +149 -0
- pyfect/channel/_run_methods.py +341 -0
- pyfect/channel/_sinks.py +88 -0
- pyfect/channel/_sources.py +238 -0
- pyfect/channel/_text.py +176 -0
- pyfect/channel/_transform_methods.py +415 -0
- pyfect/channel/_upstream.py +67 -0
- pyfect/channel_schema.py +131 -0
- pyfect/cli/__init__.py +79 -0
- pyfect/cli/_ansi.py +82 -0
- pyfect/cli/_command.py +469 -0
- pyfect/cli/_config.py +164 -0
- pyfect/cli/_environment.py +10 -0
- pyfect/cli/_help.py +195 -0
- pyfect/cli/_lexer.py +81 -0
- pyfect/cli/_parser.py +465 -0
- pyfect/cli/_settings.py +85 -0
- pyfect/cli/_suggest.py +33 -0
- pyfect/cli/_width.py +134 -0
- pyfect/cli/_wizard.py +317 -0
- pyfect/cli/argument.py +212 -0
- pyfect/cli/cli_config.py +40 -0
- pyfect/cli/cli_error.py +242 -0
- pyfect/cli/cli_output.py +305 -0
- pyfect/cli/command.py +440 -0
- pyfect/cli/completions/__init__.py +50 -0
- pyfect/cli/completions/_completer.ps1 +190 -0
- pyfect/cli/completions/_descriptor.py +182 -0
- pyfect/cli/completions/bash.py +300 -0
- pyfect/cli/completions/fish.py +164 -0
- pyfect/cli/completions/powershell.py +156 -0
- pyfect/cli/completions/zsh.py +189 -0
- pyfect/cli/flag.py +246 -0
- pyfect/cli/global_flag.py +99 -0
- pyfect/cli/help_doc.py +63 -0
- pyfect/cli/param.py +789 -0
- pyfect/cli/primitive.py +308 -0
- pyfect/cli/prompt/__init__.py +75 -0
- pyfect/cli/prompt/_auto_complete.py +205 -0
- pyfect/cli/prompt/_choices.py +104 -0
- pyfect/cli/prompt/_confirm.py +195 -0
- pyfect/cli/prompt/_core.py +512 -0
- pyfect/cli/prompt/_date.py +173 -0
- pyfect/cli/prompt/_date_parts.py +387 -0
- pyfect/cli/prompt/_file.py +372 -0
- pyfect/cli/prompt/_multi_select.py +204 -0
- pyfect/cli/prompt/_number.py +257 -0
- pyfect/cli/prompt/_select.py +117 -0
- pyfect/cli/prompt/_text.py +239 -0
- pyfect/cli/terminal_capabilities.py +118 -0
- pyfect/clock.py +150 -0
- pyfect/cluster/__init__.py +155 -0
- pyfect/cluster/_cluster_abandon.py +36 -0
- pyfect/cluster/_cluster_layer.py +122 -0
- pyfect/cluster/_entity_activation.py +16 -0
- pyfect/cluster/_entity_manager/__init__.py +22 -0
- pyfect/cluster/_entity_manager/_activation.py +540 -0
- pyfect/cluster/_entity_manager/_core.py +186 -0
- pyfect/cluster/_entity_manager/_decode.py +60 -0
- pyfect/cluster/_entity_manager/_manager.py +423 -0
- pyfect/cluster/_entity_manager/_state.py +113 -0
- pyfect/cluster/_entity_reaper.py +94 -0
- pyfect/cluster/_hash.py +13 -0
- pyfect/cluster/_interruptors.py +88 -0
- pyfect/cluster/_resource_map.py +179 -0
- pyfect/cluster/_resource_ref.py +266 -0
- pyfect/cluster/_shard_lock.py +14 -0
- pyfect/cluster/cluster_cron.py +230 -0
- pyfect/cluster/cluster_error.py +81 -0
- pyfect/cluster/cluster_http.py +210 -0
- pyfect/cluster/cluster_metrics.py +54 -0
- pyfect/cluster/cluster_schema.py +137 -0
- pyfect/cluster/cluster_socket.py +197 -0
- pyfect/cluster/cluster_workflow_engine/__init__.py +24 -0
- pyfect/cluster/cluster_workflow_engine/_engine.py +1099 -0
- pyfect/cluster/cluster_workflow_engine/_entities.py +298 -0
- pyfect/cluster/deliver_at.py +31 -0
- pyfect/cluster/entity/__init__.py +39 -0
- pyfect/cluster/entity/_entity.py +332 -0
- pyfect/cluster/entity/_handlers.py +168 -0
- pyfect/cluster/entity/_keep_alive.py +74 -0
- pyfect/cluster/entity/_queue.py +103 -0
- pyfect/cluster/entity/_request.py +34 -0
- pyfect/cluster/entity/_services.py +23 -0
- pyfect/cluster/entity/_test_client.py +237 -0
- pyfect/cluster/entity_address.py +25 -0
- pyfect/cluster/entity_id.py +20 -0
- pyfect/cluster/entity_proxy.py +161 -0
- pyfect/cluster/entity_proxy_server.py +103 -0
- pyfect/cluster/entity_resource.py +122 -0
- pyfect/cluster/entity_type.py +20 -0
- pyfect/cluster/envelope.py +307 -0
- pyfect/cluster/http_runner.py +254 -0
- pyfect/cluster/k8s_http_client.py +281 -0
- pyfect/cluster/k8s_types.py +1394 -0
- pyfect/cluster/machine_id.py +21 -0
- pyfect/cluster/message.py +219 -0
- pyfect/cluster/message_storage/__init__.py +67 -0
- pyfect/cluster/message_storage/_encoded.py +393 -0
- pyfect/cluster/message_storage/_memory.py +386 -0
- pyfect/cluster/message_storage/_noop.py +109 -0
- pyfect/cluster/message_storage/_service.py +388 -0
- pyfect/cluster/reply.py +236 -0
- pyfect/cluster/runner.py +32 -0
- pyfect/cluster/runner_address.py +23 -0
- pyfect/cluster/runner_health.py +93 -0
- pyfect/cluster/runner_server.py +286 -0
- pyfect/cluster/runner_storage.py +212 -0
- pyfect/cluster/runners/__init__.py +55 -0
- pyfect/cluster/runners/_local.py +460 -0
- pyfect/cluster/runners/_noop.py +57 -0
- pyfect/cluster/runners/_rpc.py +290 -0
- pyfect/cluster/runners/_service.py +113 -0
- pyfect/cluster/shard_id.py +39 -0
- pyfect/cluster/sharding/__init__.py +12 -0
- pyfect/cluster/sharding/_acquisition.py +490 -0
- pyfect/cluster/sharding/_clients.py +271 -0
- pyfect/cluster/sharding/_make.py +388 -0
- pyfect/cluster/sharding/_routing.py +296 -0
- pyfect/cluster/sharding/_runners_sync.py +198 -0
- pyfect/cluster/sharding/_service.py +91 -0
- pyfect/cluster/sharding/_singletons.py +183 -0
- pyfect/cluster/sharding/_state.py +296 -0
- pyfect/cluster/sharding/_storage_loop.py +421 -0
- pyfect/cluster/sharding_config.py +230 -0
- pyfect/cluster/sharding_registration_event.py +28 -0
- pyfect/cluster/single_runner.py +49 -0
- pyfect/cluster/singleton.py +36 -0
- pyfect/cluster/singleton_address.py +20 -0
- pyfect/cluster/snowflake.py +133 -0
- pyfect/cluster/socket_runner.py +67 -0
- pyfect/cluster/sql_message_storage.py +1001 -0
- pyfect/cluster/sql_runner_storage.py +841 -0
- pyfect/cluster/test_runner.py +23 -0
- pyfect/config/__init__.py +93 -0
- pyfect/config/_all.py +96 -0
- pyfect/config/_collections.py +291 -0
- pyfect/config/_config.py +261 -0
- pyfect/config/_constructors.py +106 -0
- pyfect/config/_error.py +27 -0
- pyfect/config/_leaf.py +92 -0
- pyfect/config/_scalars.py +193 -0
- pyfect/config/_schema.py +340 -0
- pyfect/config_provider/__init__.py +60 -0
- pyfect/config_provider/_dir.py +84 -0
- pyfect/config_provider/_dotenv.py +127 -0
- pyfect/config_provider/_env.py +79 -0
- pyfect/config_provider/_layers.py +80 -0
- pyfect/config_provider/_node.py +90 -0
- pyfect/config_provider/_provider.py +118 -0
- pyfect/config_provider/_unknown.py +62 -0
- pyfect/console/__init__.py +63 -0
- pyfect/console/_effects.py +134 -0
- pyfect/console/_live.py +156 -0
- pyfect/console/_service.py +49 -0
- pyfect/console/_table.py +90 -0
- pyfect/context.py +414 -0
- pyfect/cron/__init__.py +30 -0
- pyfect/cron/_model.py +183 -0
- pyfect/cron/_search.py +268 -0
- pyfect/cron/_text.py +202 -0
- pyfect/cron/_wall.py +42 -0
- pyfect/crypto.py +257 -0
- pyfect/date_time/__init__.py +77 -0
- pyfect/date_time/_calendar.py +209 -0
- pyfect/date_time/_current.py +118 -0
- pyfect/date_time/_epoch.py +22 -0
- pyfect/date_time/_iso.py +80 -0
- pyfect/date_time/_zones.py +135 -0
- pyfect/date_time/intl.py +144 -0
- pyfect/deferred.py +145 -0
- pyfect/devtools/__init__.py +24 -0
- pyfect/devtools/client.py +323 -0
- pyfect/devtools/layers.py +32 -0
- pyfect/devtools/schema.py +301 -0
- pyfect/devtools/server.py +72 -0
- pyfect/duration.py +295 -0
- pyfect/effect.py +565 -0
- pyfect/effectable.py +37 -0
- pyfect/encoding/__init__.py +62 -0
- pyfect/encoding/_archive.py +197 -0
- pyfect/encoding/_json_array_scanner.py +477 -0
- pyfect/encoding/base64.py +23 -0
- pyfect/encoding/base64_url.py +19 -0
- pyfect/encoding/encoding_error.py +17 -0
- pyfect/encoding/hex.py +28 -0
- pyfect/encoding/ini.py +188 -0
- pyfect/encoding/json_array.py +246 -0
- pyfect/encoding/ndjson.py +461 -0
- pyfect/encoding/schema_binary/__init__.py +257 -0
- pyfect/encoding/schema_binary/_channels.py +173 -0
- pyfect/encoding/schema_binary/_compile.py +584 -0
- pyfect/encoding/schema_binary/_decode.py +848 -0
- pyfect/encoding/schema_binary/_dictionary.py +90 -0
- pyfect/encoding/schema_binary/_encode.py +838 -0
- pyfect/encoding/schema_binary/_fingerprint.py +168 -0
- pyfect/encoding/schema_binary/_layout.py +350 -0
- pyfect/encoding/schema_binary/_literals.py +47 -0
- pyfect/encoding/schema_binary/_parser.py +163 -0
- pyfect/encoding/schema_binary/_reader.py +148 -0
- pyfect/encoding/schema_binary/_target.py +310 -0
- pyfect/encoding/schema_binary/_values.py +133 -0
- pyfect/encoding/schema_binary/_wire.py +253 -0
- pyfect/encoding/sse.py +476 -0
- pyfect/encoding/tar/__init__.py +396 -0
- pyfect/encoding/tar/_decoder.py +220 -0
- pyfect/encoding/tar/_encoder.py +83 -0
- pyfect/encoding/tar/_header.py +494 -0
- pyfect/encoding/tar/_model.py +82 -0
- pyfect/encoding/toml.py +375 -0
- pyfect/encoding/xml/__init__.py +222 -0
- pyfect/encoding/xml/_chars.py +119 -0
- pyfect/encoding/xml/_collector.py +106 -0
- pyfect/encoding/xml/_inputs.py +126 -0
- pyfect/encoding/xml/_model.py +114 -0
- pyfect/encoding/xml/_parser.py +694 -0
- pyfect/encoding/xml/_scanner.py +180 -0
- pyfect/encoding/xml/_writer.py +238 -0
- pyfect/encoding/yaml.py +739 -0
- pyfect/encoding/zip/__init__.py +392 -0
- pyfect/encoding/zip/_decoder.py +378 -0
- pyfect/encoding/zip/_format.py +122 -0
- pyfect/encoding/zip/_inflate.py +65 -0
- pyfect/encoding/zip/_model.py +86 -0
- pyfect/encoding/zip/_writer.py +311 -0
- pyfect/error_reporter.py +240 -0
- pyfect/errors.py +321 -0
- pyfect/eventlog/__init__.py +77 -0
- pyfect/eventlog/_conflicts.py +94 -0
- pyfect/eventlog/_crypto.py +64 -0
- pyfect/eventlog/_identity.py +26 -0
- pyfect/eventlog/_identity_derivation.py +79 -0
- pyfect/eventlog/_transferable_bytes.py +55 -0
- pyfect/eventlog/_uuid.py +33 -0
- pyfect/eventlog/event.py +115 -0
- pyfect/eventlog/event_group.py +55 -0
- pyfect/eventlog/event_journal.py +630 -0
- pyfect/eventlog/event_log.py +1039 -0
- pyfect/eventlog/event_log_encryption.py +219 -0
- pyfect/eventlog/event_log_message.py +357 -0
- pyfect/eventlog/event_log_remote.py +465 -0
- pyfect/eventlog/event_log_server.py +442 -0
- pyfect/eventlog/event_log_server_encrypted.py +322 -0
- pyfect/eventlog/event_log_server_unencrypted.py +814 -0
- pyfect/eventlog/event_log_session_auth.py +250 -0
- pyfect/eventlog/event_row.py +423 -0
- pyfect/eventlog/sql_event_journal.py +692 -0
- pyfect/eventlog/sql_event_log_server_encrypted.py +440 -0
- pyfect/eventlog/sql_event_log_server_unencrypted.py +568 -0
- pyfect/execution_plan/__init__.py +42 -0
- pyfect/execution_plan/_events.py +128 -0
- pyfect/execution_plan/_model.py +108 -0
- pyfect/execution_plan/_run.py +176 -0
- pyfect/exit.py +205 -0
- pyfect/fiber_handle.py +305 -0
- pyfect/fiber_map.py +350 -0
- pyfect/fiber_set.py +232 -0
- pyfect/file_system/__init__.py +69 -0
- pyfect/file_system/_copy.py +116 -0
- pyfect/file_system/_errors.py +32 -0
- pyfect/file_system/_file.py +177 -0
- pyfect/file_system/_live.py +351 -0
- pyfect/file_system/_model.py +72 -0
- pyfect/file_system/_noop.py +189 -0
- pyfect/file_system/_service.py +336 -0
- pyfect/file_system/_stat.py +69 -0
- pyfect/file_system/_watch.py +107 -0
- pyfect/filters.py +245 -0
- pyfect/graph/__init__.py +101 -0
- pyfect/graph/_all_pairs.py +150 -0
- pyfect/graph/_combine.py +259 -0
- pyfect/graph/_compact.py +143 -0
- pyfect/graph/_components.py +200 -0
- pyfect/graph/_cycles.py +144 -0
- pyfect/graph/_enumerate.py +168 -0
- pyfect/graph/_errors.py +51 -0
- pyfect/graph/_flow.py +164 -0
- pyfect/graph/_forest.py +109 -0
- pyfect/graph/_graph.py +269 -0
- pyfect/graph/_lowlink.py +98 -0
- pyfect/graph/_matching.py +101 -0
- pyfect/graph/_mutate.py +211 -0
- pyfect/graph/_query.py +138 -0
- pyfect/graph/_records.py +119 -0
- pyfect/graph/_render.py +109 -0
- pyfect/graph/_shortest.py +274 -0
- pyfect/graph/_snapshot.py +50 -0
- pyfect/graph/_store.py +136 -0
- pyfect/graph/_traverse.py +289 -0
- pyfect/graph/_view.py +384 -0
- pyfect/graph/_walker.py +70 -0
- pyfect/graphql/__init__.py +87 -0
- pyfect/graphql/_codec.py +117 -0
- pyfect/graphql/_http.py +309 -0
- pyfect/graphql/_schemas.py +39 -0
- pyfect/graphql/_slices.py +110 -0
- pyfect/graphql/_template.py +111 -0
- pyfect/graphql/_types.py +115 -0
- pyfect/graphql/_ws.py +395 -0
- pyfect/graphql/_ws_messages.py +120 -0
- pyfect/graphql/client.py +428 -0
- pyfect/graphql/client_error.py +285 -0
- pyfect/graphql/group.py +142 -0
- pyfect/graphql/middleware.py +98 -0
- pyfect/graphql/operation.py +409 -0
- pyfect/graphql/protocol.py +119 -0
- pyfect/graphql/resolver.py +574 -0
- pyfect/graphql/template.py +61 -0
- pyfect/hash_map.py +317 -0
- pyfect/hash_ring.py +206 -0
- pyfect/hash_set.py +163 -0
- pyfect/http/__init__.py +125 -0
- pyfect/http/_asgi_listener.py +272 -0
- pyfect/http/_authority.py +63 -0
- pyfect/http/_brotli.py +27 -0
- pyfect/http/_compression.py +52 -0
- pyfect/http/_field.py +18 -0
- pyfect/http/_foreign_path.py +27 -0
- pyfect/http/_form_data.py +34 -0
- pyfect/http/_httpx_options.py +120 -0
- pyfect/http/_in_memory.py +76 -0
- pyfect/http/_request_state.py +59 -0
- pyfect/http/_vary.py +14 -0
- pyfect/http/abort.py +65 -0
- pyfect/http/asgi/__init__.py +59 -0
- pyfect/http/asgi/_app.py +155 -0
- pyfect/http/asgi/_http.py +201 -0
- pyfect/http/asgi/_mount.py +263 -0
- pyfect/http/asgi/_scope.py +86 -0
- pyfect/http/asgi/_serve.py +72 -0
- pyfect/http/asgi/_types.py +13 -0
- pyfect/http/asgi/_web_socket.py +325 -0
- pyfect/http/body.py +437 -0
- pyfect/http/client/__init__.py +111 -0
- pyfect/http/client/_circuit_breaker.py +113 -0
- pyfect/http/client/_cookie_jar.py +120 -0
- pyfect/http/client/_make.py +252 -0
- pyfect/http/client/_rate_limit.py +423 -0
- pyfect/http/client/_redirects.py +93 -0
- pyfect/http/client/_retry.py +115 -0
- pyfect/http/client/_service.py +497 -0
- pyfect/http/cookies.py +444 -0
- pyfect/http/error.py +188 -0
- pyfect/http/etag.py +96 -0
- pyfect/http/file_like.py +49 -0
- pyfect/http/find_my_way/__init__.py +10 -0
- pyfect/http/find_my_way/_nodes.py +186 -0
- pyfect/http/find_my_way/_paths.py +43 -0
- pyfect/http/find_my_way/_query_string.py +67 -0
- pyfect/http/find_my_way/_router.py +401 -0
- pyfect/http/headers.py +343 -0
- pyfect/http/http_effect.py +198 -0
- pyfect/http/httpx_client.py +291 -0
- pyfect/http/hypercorn_server.py +122 -0
- pyfect/http/incoming_message.py +140 -0
- pyfect/http/live_server/__init__.py +191 -0
- pyfect/http/live_server/_body.py +164 -0
- pyfect/http/live_server/_exchange.py +321 -0
- pyfect/http/live_server/_head.py +194 -0
- pyfect/http/live_server/_inbound.py +156 -0
- pyfect/http/live_server/_listener.py +211 -0
- pyfect/http/live_server/_serve.py +225 -0
- pyfect/http/live_server/_timers.py +20 -0
- pyfect/http/live_server/_upgrade.py +108 -0
- pyfect/http/live_server/_websocket.py +286 -0
- pyfect/http/live_server/_ws_frames.py +242 -0
- pyfect/http/method.py +30 -0
- pyfect/http/middleware/__init__.py +61 -0
- pyfect/http/middleware/_compression.py +156 -0
- pyfect/http/middleware/_cors.py +129 -0
- pyfect/http/middleware/_decompression.py +274 -0
- pyfect/http/middleware/_forwarded.py +113 -0
- pyfect/http/middleware/_logger.py +73 -0
- pyfect/http/middleware/_search_params.py +34 -0
- pyfect/http/middleware/_tracer.py +153 -0
- pyfect/http/middleware/_types.py +16 -0
- pyfect/http/mime/__init__.py +69 -0
- pyfect/http/mime/_types.py +361 -0
- pyfect/http/multipart.py +613 -0
- pyfect/http/multipart_parser/__init__.py +348 -0
- pyfect/http/multipart_parser/_content_type.py +109 -0
- pyfect/http/multipart_parser/_headers.py +206 -0
- pyfect/http/multipart_parser/_search.py +85 -0
- pyfect/http/origin.py +146 -0
- pyfect/http/platform.py +593 -0
- pyfect/http/prometheus.py +34 -0
- pyfect/http/request.py +384 -0
- pyfect/http/response.py +474 -0
- pyfect/http/router/__init__.py +82 -0
- pyfect/http/router/_api.py +462 -0
- pyfect/http/router/_in_memory.py +76 -0
- pyfect/http/router/_markers.py +47 -0
- pyfect/http/router/_middleware.py +181 -0
- pyfect/http/router/_middleware_stack.py +51 -0
- pyfect/http/router/_provided.py +31 -0
- pyfect/http/router/_route.py +114 -0
- pyfect/http/router/_router.py +308 -0
- pyfect/http/schema.py +276 -0
- pyfect/http/server.py +199 -0
- pyfect/http/server_error.py +261 -0
- pyfect/http/server_request.py +729 -0
- pyfect/http/server_respondable.py +77 -0
- pyfect/http/server_response.py +569 -0
- pyfect/http/static_server/__init__.py +257 -0
- pyfect/http/static_server/_conditional.py +72 -0
- pyfect/http/static_server/_mime.py +55 -0
- pyfect/http/static_server/_range.py +53 -0
- pyfect/http/status.py +51 -0
- pyfect/http/template.py +161 -0
- pyfect/http/trace_context.py +91 -0
- pyfect/http/url.py +442 -0
- pyfect/http/url_params.py +304 -0
- pyfect/http/uvicorn_server.py +119 -0
- pyfect/http/web.py +171 -0
- pyfect/http/web_handler.py +463 -0
- pyfect/http/wire.py +209 -0
- pyfect/http_api/__init__.py +83 -0
- pyfect/http_api/_assets/scalar.js +8 -0
- pyfect/http_api/_assets/swagger-ui.css +3 -0
- pyfect/http_api/_assets/swagger-ui.js +4 -0
- pyfect/http_api/_client_method.py +220 -0
- pyfect/http_api/_client_payload.py +144 -0
- pyfect/http_api/_client_response.py +377 -0
- pyfect/http_api/_handlers.py +282 -0
- pyfect/http_api/_html.py +38 -0
- pyfect/http_api/_media_type.py +6 -0
- pyfect/http_api/_openapi_responses.py +164 -0
- pyfect/http_api/_openapi_schemas.py +163 -0
- pyfect/http_api/_path.py +102 -0
- pyfect/http_api/_payload.py +260 -0
- pyfect/http_api/_response.py +363 -0
- pyfect/http_api/_security.py +174 -0
- pyfect/http_api/api.py +265 -0
- pyfect/http_api/builder.py +447 -0
- pyfect/http_api/client.py +474 -0
- pyfect/http_api/endpoint.py +969 -0
- pyfect/http_api/error.py +280 -0
- pyfect/http_api/group.py +215 -0
- pyfect/http_api/middleware.py +232 -0
- pyfect/http_api/openapi.py +396 -0
- pyfect/http_api/scalar.py +143 -0
- pyfect/http_api/schema.py +713 -0
- pyfect/http_api/security.py +91 -0
- pyfect/http_api/swagger.py +69 -0
- pyfect/http_api/test.py +105 -0
- pyfect/json_patch.py +255 -0
- pyfect/json_pointer.py +100 -0
- pyfect/json_schema/__init__.py +66 -0
- pyfect/json_schema/_api.py +96 -0
- pyfect/json_schema/_converter.py +258 -0
- pyfect/json_schema/_draft04.py +198 -0
- pyfect/json_schema/_draft07.py +203 -0
- pyfect/json_schema/_keywords.py +202 -0
- pyfect/json_schema/_model.py +201 -0
- pyfect/json_schema/_openapi.py +208 -0
- pyfect/latch.py +127 -0
- pyfect/layer.py +479 -0
- pyfect/layer_map.py +279 -0
- pyfect/layer_ref.py +208 -0
- pyfect/logger/__init__.py +91 -0
- pyfect/logger/_batched.py +49 -0
- pyfect/logger/_console.py +116 -0
- pyfect/logger/_file.py +31 -0
- pyfect/logger/_formats.py +104 -0
- pyfect/logger/_layer.py +50 -0
- pyfect/logger/_level.py +53 -0
- pyfect/logger/_log.py +142 -0
- pyfect/logger/_model.py +163 -0
- pyfect/logger/_refs.py +78 -0
- pyfect/logger/_stdlib.py +54 -0
- pyfect/logger/_text.py +263 -0
- pyfect/logger/_tracer.py +35 -0
- pyfect/managed_runtime.py +393 -0
- pyfect/metric/__init__.py +119 -0
- pyfect/metric/_boundaries.py +27 -0
- pyfect/metric/_kinds.py +417 -0
- pyfect/metric/_metric.py +302 -0
- pyfect/metric/_prometheus.py +150 -0
- pyfect/metric/_refs.py +78 -0
- pyfect/metric/_runtime.py +75 -0
- pyfect/metric/_snapshot.py +97 -0
- pyfect/metric/_state.py +74 -0
- pyfect/msgspec.py +98 -0
- pyfect/mutable_hash_map.py +140 -0
- pyfect/mutable_hash_set.py +95 -0
- pyfect/net/__init__.py +18 -0
- pyfect/net/_errors.py +29 -0
- pyfect/net/address_resolver.py +179 -0
- pyfect/net/dns/__init__.py +92 -0
- pyfect/net/dns/_error.py +43 -0
- pyfect/net/dns/_records.py +335 -0
- pyfect/net/dns/_service.py +287 -0
- pyfect/net/dns/_system.py +89 -0
- pyfect/net/host.py +311 -0
- pyfect/net/ip_interface.py +202 -0
- pyfect/net/ip_network.py +250 -0
- pyfect/net/net_address/__init__.py +245 -0
- pyfect/net/net_address/_inet.py +412 -0
- pyfect/net/net_address/_ip.py +348 -0
- pyfect/net/net_address/_mac.py +119 -0
- pyfect/optic/__init__.py +50 -0
- pyfect/optic/_builtins.py +90 -0
- pyfect/optic/_data.py +97 -0
- pyfect/optic/_optic.py +422 -0
- pyfect/optic/_steps.py +151 -0
- pyfect/option.py +362 -0
- pyfect/otel/__init__.py +22 -0
- pyfect/otel/_attributes.py +67 -0
- pyfect/otel/_producer.py +322 -0
- pyfect/otel/_shutdown.py +26 -0
- pyfect/otel/_span.py +292 -0
- pyfect/otel/_wrapper.py +92 -0
- pyfect/otel/logger.py +154 -0
- pyfect/otel/metrics.py +126 -0
- pyfect/otel/resource.py +111 -0
- pyfect/otel/sdk.py +195 -0
- pyfect/otel/tracer.py +157 -0
- pyfect/partitioned_semaphore.py +211 -0
- pyfect/path.py +304 -0
- pyfect/persistence/__init__.py +77 -0
- pyfect/persistence/_columns.py +32 -0
- pyfect/persistence/_error.py +14 -0
- pyfect/persistence/_seconds.py +68 -0
- pyfect/persistence/_sql_keys.py +47 -0
- pyfect/persistence/circuit_breaker/__init__.py +120 -0
- pyfect/persistence/circuit_breaker/_breaker.py +317 -0
- pyfect/persistence/circuit_breaker/_errors.py +67 -0
- pyfect/persistence/circuit_breaker/_memory.py +243 -0
- pyfect/persistence/circuit_breaker/_redis.py +509 -0
- pyfect/persistence/circuit_breaker/_store.py +60 -0
- pyfect/persistence/circuit_breaker/_types.py +191 -0
- pyfect/persistence/key_value_store/__init__.py +51 -0
- pyfect/persistence/key_value_store/_layers.py +216 -0
- pyfect/persistence/key_value_store/_schema_store.py +91 -0
- pyfect/persistence/key_value_store/_sql.py +197 -0
- pyfect/persistence/key_value_store/_store.py +212 -0
- pyfect/persistence/persistable.py +164 -0
- pyfect/persistence/persisted_cache.py +159 -0
- pyfect/persistence/persisted_queue/__init__.py +75 -0
- pyfect/persistence/persisted_queue/_error.py +42 -0
- pyfect/persistence/persisted_queue/_mailbox.py +101 -0
- pyfect/persistence/persisted_queue/_memory.py +182 -0
- pyfect/persistence/persisted_queue/_queue.py +272 -0
- pyfect/persistence/persisted_queue/_redis.py +455 -0
- pyfect/persistence/persisted_queue/_redis_scripts.py +395 -0
- pyfect/persistence/persisted_queue/_sql.py +334 -0
- pyfect/persistence/persisted_queue/_sql_migrations.py +319 -0
- pyfect/persistence/persisted_queue/_sql_statements.py +441 -0
- pyfect/persistence/persisted_queue/_store.py +147 -0
- pyfect/persistence/persistence/__init__.py +84 -0
- pyfect/persistence/persistence/_kvs.py +135 -0
- pyfect/persistence/persistence/_memory.py +62 -0
- pyfect/persistence/persistence/_redis.py +162 -0
- pyfect/persistence/persistence/_service.py +276 -0
- pyfect/persistence/persistence/_sql.py +499 -0
- pyfect/persistence/rate_limiter/__init__.py +82 -0
- pyfect/persistence/rate_limiter/_errors.py +59 -0
- pyfect/persistence/rate_limiter/_limiter.py +325 -0
- pyfect/persistence/rate_limiter/_memory.py +217 -0
- pyfect/persistence/rate_limiter/_redis.py +485 -0
- pyfect/persistence/rate_limiter/_store.py +89 -0
- pyfect/persistence/redis/__init__.py +52 -0
- pyfect/persistence/redis/_error.py +12 -0
- pyfect/persistence/redis/_make.py +234 -0
- pyfect/persistence/redis/_script.py +78 -0
- pyfect/persistence/redis/_service.py +104 -0
- pyfect/persistence/redis_py.py +303 -0
- pyfect/persistence/user_directories.py +195 -0
- pyfect/platform.py +26 -0
- pyfect/platform_error.py +256 -0
- pyfect/pool/__init__.py +36 -0
- pyfect/pool/_pool.py +707 -0
- pyfect/pool/_state.py +161 -0
- pyfect/pool/_ttl.py +105 -0
- pyfect/pool/_waiters.py +116 -0
- pyfect/primary_key.py +35 -0
- pyfect/process/__init__.py +82 -0
- pyfect/process/_child.py +213 -0
- pyfect/process/_command.py +358 -0
- pyfect/process/_handle.py +77 -0
- pyfect/process/_live.py +455 -0
- pyfect/process/_os.py +78 -0
- pyfect/process/_pipes.py +291 -0
- pyfect/process/_spawner.py +75 -0
- pyfect/pubsub/__init__.py +33 -0
- pyfect/pubsub/_atomic.py +181 -0
- pyfect/pubsub/_hub.py +214 -0
- pyfect/pubsub/_pubsub.py +214 -0
- pyfect/pubsub/_replay.py +107 -0
- pyfect/pubsub/_subscription.py +226 -0
- pyfect/pull.py +132 -0
- pyfect/py.typed +0 -0
- pyfect/pydantic.py +188 -0
- pyfect/queue/__init__.py +33 -0
- pyfect/queue/_queue.py +525 -0
- pyfect/queue/_state.py +111 -0
- pyfect/queue/_views.py +55 -0
- pyfect/random.py +146 -0
- pyfect/rc_map.py +363 -0
- pyfect/rc_ref.py +271 -0
- pyfect/reactivity/__init__.py +53 -0
- pyfect/reactivity/_async_result_schema.py +227 -0
- pyfect/reactivity/_hydration.py +18 -0
- pyfect/reactivity/_live.py +21 -0
- pyfect/reactivity/_reactivity.py +256 -0
- pyfect/reactivity/_result_effect.py +17 -0
- pyfect/reactivity/_time.py +11 -0
- pyfect/reactivity/async_result.py +679 -0
- pyfect/reactivity/atom/__init__.py +143 -0
- pyfect/reactivity/atom/_combinators.py +223 -0
- pyfect/reactivity/atom/_constructors.py +80 -0
- pyfect/reactivity/atom/_conversions.py +79 -0
- pyfect/reactivity/atom/_effect.py +252 -0
- pyfect/reactivity/atom/_fn.py +341 -0
- pyfect/reactivity/atom/_kvs.py +101 -0
- pyfect/reactivity/atom/_optimistic.py +162 -0
- pyfect/reactivity/atom/_runtime.py +323 -0
- pyfect/reactivity/atom/_serializable.py +65 -0
- pyfect/reactivity/atom/_subscription_ref.py +102 -0
- pyfect/reactivity/atom/_swr.py +69 -0
- pyfect/reactivity/atom/_types.py +435 -0
- pyfect/reactivity/atom_http_api.py +667 -0
- pyfect/reactivity/atom_ref.py +260 -0
- pyfect/reactivity/atom_registry/__init__.py +219 -0
- pyfect/reactivity/atom_registry/_batch.py +96 -0
- pyfect/reactivity/atom_registry/_dispatcher.py +60 -0
- pyfect/reactivity/atom_registry/_lifetime.py +202 -0
- pyfect/reactivity/atom_registry/_node.py +423 -0
- pyfect/reactivity/atom_registry/_registry.py +380 -0
- pyfect/reactivity/atom_rpc.py +283 -0
- pyfect/reactivity/hydration.py +200 -0
- pyfect/redacted.py +83 -0
- pyfect/ref.py +129 -0
- pyfect/resource.py +98 -0
- pyfect/result.py +306 -0
- pyfect/rpc/__init__.py +64 -0
- pyfect/rpc/_encoded_schema.py +86 -0
- pyfect/rpc/_json_rpc.py +246 -0
- pyfect/rpc/_methods.py +177 -0
- pyfect/rpc/_rpc.py +498 -0
- pyfect/rpc/client/__init__.py +100 -0
- pyfect/rpc/client/_core.py +449 -0
- pyfect/rpc/client/_http.py +257 -0
- pyfect/rpc/client/_protocol.py +171 -0
- pyfect/rpc/client/_serialized.py +321 -0
- pyfect/rpc/client/_transports.py +328 -0
- pyfect/rpc/client/_worker.py +283 -0
- pyfect/rpc/client_error.py +35 -0
- pyfect/rpc/group.py +296 -0
- pyfect/rpc/message.py +299 -0
- pyfect/rpc/middleware.py +182 -0
- pyfect/rpc/schema.py +89 -0
- pyfect/rpc/serialization.py +366 -0
- pyfect/rpc/server/__init__.py +181 -0
- pyfect/rpc/server/_core.py +549 -0
- pyfect/rpc/server/_protocol.py +123 -0
- pyfect/rpc/server/_serialized.py +363 -0
- pyfect/rpc/server/_stdio.py +101 -0
- pyfect/rpc/server/_transports.py +427 -0
- pyfect/rpc/server/_worker.py +108 -0
- pyfect/rpc/test.py +74 -0
- pyfect/rpc/worker.py +92 -0
- pyfect/run.py +393 -0
- pyfect/schedule/__init__.py +91 -0
- pyfect/schedule/_combinators.py +168 -0
- pyfect/schedule/_constructors.py +223 -0
- pyfect/schedule/_cron.py +49 -0
- pyfect/schedule/_drive.py +191 -0
- pyfect/schedule/_model.py +405 -0
- pyfect/scheduler.py +575 -0
- pyfect/schema/__init__.py +866 -0
- pyfect/schema/_adapters.py +39 -0
- pyfect/schema/_arbitrary_constraint.py +242 -0
- pyfect/schema/_arbitrary_graph.py +325 -0
- pyfect/schema/_arbitrary_links.py +179 -0
- pyfect/schema/_arbitrary_number.py +112 -0
- pyfect/schema/_arrays.py +287 -0
- pyfect/schema/_assignable.py +118 -0
- pyfect/schema/_boolean_literals.py +31 -0
- pyfect/schema/_checks_number.py +216 -0
- pyfect/schema/_checks_order.py +140 -0
- pyfect/schema/_checks_size.py +277 -0
- pyfect/schema/_checks_string.py +241 -0
- pyfect/schema/_class.py +569 -0
- pyfect/schema/_codec_derivation.py +227 -0
- pyfect/schema/_codec_links.py +221 -0
- pyfect/schema/_codecs.py +316 -0
- pyfect/schema/_collections.py +172 -0
- pyfect/schema/_constructor_default.py +89 -0
- pyfect/schema/_data.py +440 -0
- pyfect/schema/_data_codecs.py +145 -0
- pyfect/schema/_decimal_regexp.py +243 -0
- pyfect/schema/_declare.py +68 -0
- pyfect/schema/_derive.py +292 -0
- pyfect/schema/_duration_codecs.py +27 -0
- pyfect/schema/_ecma_pattern.py +416 -0
- pyfect/schema/_error.py +26 -0
- pyfect/schema/_error_instance.py +222 -0
- pyfect/schema/_graph.py +271 -0
- pyfect/schema/_instances.py +240 -0
- pyfect/schema/_jsonschema/__init__.py +90 -0
- pyfect/schema/_jsonschema/_annotations.py +114 -0
- pyfect/schema/_jsonschema/_checks.py +253 -0
- pyfect/schema/_jsonschema/_compile.py +346 -0
- pyfect/schema/_jsonschema/_exact.py +19 -0
- pyfect/schema/_jsonschema/_merge.py +211 -0
- pyfect/schema/_jsonschema/_objects.py +231 -0
- pyfect/schema/_jsonschema/_references.py +221 -0
- pyfect/schema/_jsonschema/_template.py +69 -0
- pyfect/schema/_literals.py +147 -0
- pyfect/schema/_middleware.py +209 -0
- pyfect/schema/_net.py +522 -0
- pyfect/schema/_options.py +49 -0
- pyfect/schema/_parse.py +351 -0
- pyfect/schema/_parsed_text.py +37 -0
- pyfect/schema/_primitives.py +48 -0
- pyfect/schema/_record.py +41 -0
- pyfect/schema/_refine.py +81 -0
- pyfect/schema/_schema.py +158 -0
- pyfect/schema/_semver.py +144 -0
- pyfect/schema/_strategy/__init__.py +258 -0
- pyfect/schema/_strategy/_big_decimal.py +203 -0
- pyfect/schema/_strategy/_composites.py +119 -0
- pyfect/schema/_strategy/_leaves.py +94 -0
- pyfect/schema/_strategy/_net.py +133 -0
- pyfect/schema/_struct.py +87 -0
- pyfect/schema/_struct_keys.py +128 -0
- pyfect/schema/_tagged.py +254 -0
- pyfect/schema/_template_literal.py +172 -0
- pyfect/schema/_to_codec_array_from_single.py +115 -0
- pyfect/schema/_to_codec_go_json.py +174 -0
- pyfect/schema/_to_codec_json.py +73 -0
- pyfect/schema/_to_codec_string_tree.py +115 -0
- pyfect/schema/_to_differ_json_patch.py +44 -0
- pyfect/schema/_to_encoder_xml.py +119 -0
- pyfect/schema/_to_equivalence.py +186 -0
- pyfect/schema/_to_formatter.py +180 -0
- pyfect/schema/_to_iso.py +73 -0
- pyfect/schema/_to_strategy.py +31 -0
- pyfect/schema/_transform.py +210 -0
- pyfect/schema/_tree.py +27 -0
- pyfect/schema/_type_walk.py +53 -0
- pyfect/schema/_undefined.py +58 -0
- pyfect/schema/_value_codecs.py +84 -0
- pyfect/schema/_values.py +123 -0
- pyfect/schema/_void.py +86 -0
- pyfect/schema/ast/__init__.py +227 -0
- pyfect/schema/ast/_annotations.py +43 -0
- pyfect/schema/ast/_arrays.py +210 -0
- pyfect/schema/ast/_base.py +295 -0
- pyfect/schema/ast/_candidates.py +309 -0
- pyfect/schema/ast/_check.py +181 -0
- pyfect/schema/ast/_class.py +159 -0
- pyfect/schema/ast/_constructors.py +65 -0
- pyfect/schema/ast/_declaration.py +97 -0
- pyfect/schema/ast/_encoding.py +111 -0
- pyfect/schema/ast/_json.py +129 -0
- pyfect/schema/ast/_keywords.py +125 -0
- pyfect/schema/ast/_literal.py +157 -0
- pyfect/schema/ast/_objects.py +356 -0
- pyfect/schema/ast/_record.py +36 -0
- pyfect/schema/ast/_result.py +116 -0
- pyfect/schema/ast/_steps.py +209 -0
- pyfect/schema/ast/_suspend.py +65 -0
- pyfect/schema/ast/_template_literal.py +256 -0
- pyfect/schema/ast/_union.py +172 -0
- pyfect/schema/getter/__init__.py +136 -0
- pyfect/schema/getter/_binary.py +104 -0
- pyfect/schema/getter/_coerce.py +196 -0
- pyfect/schema/getter/_getter.py +243 -0
- pyfect/schema/getter/_make.py +151 -0
- pyfect/schema/getter/_text.py +141 -0
- pyfect/schema/getter/_tree_record.py +170 -0
- pyfect/schema/issue.py +420 -0
- pyfect/schema/model/__init__.py +120 -0
- pyfect/schema/model/_class.py +245 -0
- pyfect/schema/model/_dates.py +103 -0
- pyfect/schema/model/_fields.py +77 -0
- pyfect/schema/model/_toolkit.py +114 -0
- pyfect/schema/model/_uuid.py +69 -0
- pyfect/schema/representation/__init__.py +424 -0
- pyfect/schema/representation/_builtin_revivers.py +331 -0
- pyfect/schema/representation/_code.py +106 -0
- pyfect/schema/representation/_code_builtins.py +281 -0
- pyfect/schema/representation/_code_document.py +563 -0
- pyfect/schema/representation/_code_sort.py +111 -0
- pyfect/schema/representation/_from.py +278 -0
- pyfect/schema/representation/_import/__init__.py +78 -0
- pyfect/schema/representation/_import/_finalize.py +121 -0
- pyfect/schema/representation/_import/_intersect.py +467 -0
- pyfect/schema/representation/_import/_js_order.py +66 -0
- pyfect/schema/representation/_import/_keywords.py +224 -0
- pyfect/schema/representation/_import/_nodes.py +199 -0
- pyfect/schema/representation/_import/_translate.py +455 -0
- pyfect/schema/representation/_json.py +520 -0
- pyfect/schema/representation/_json_schema.py +178 -0
- pyfect/schema/representation/_model.py +284 -0
- pyfect/schema/representation/_revivers.py +89 -0
- pyfect/schema/representation/_to.py +249 -0
- pyfect/schema/transformation/__init__.py +99 -0
- pyfect/schema/transformation/_builtins.py +314 -0
- pyfect/schema/transformation/_durations.py +150 -0
- pyfect/schema/transformation/_transformation.py +177 -0
- pyfect/schema/variant_schema/__init__.py +69 -0
- pyfect/schema/variant_schema/_class.py +188 -0
- pyfect/schema/variant_schema/_defaults.py +30 -0
- pyfect/schema/variant_schema/_field.py +32 -0
- pyfect/schema/variant_schema/_struct.py +130 -0
- pyfect/schema/variant_schema/_toolkit.py +160 -0
- pyfect/scope.py +374 -0
- pyfect/scoped_ref.py +124 -0
- pyfect/semaphore.py +276 -0
- pyfect/semver.py +403 -0
- pyfect/semver_range.py +420 -0
- pyfect/sink/__init__.py +131 -0
- pyfect/sink/_combinators.py +151 -0
- pyfect/sink/_constructors.py +187 -0
- pyfect/sink/_context.py +87 -0
- pyfect/sink/_folds.py +336 -0
- pyfect/sink/_for_each.py +87 -0
- pyfect/sink/_sink.py +265 -0
- pyfect/sink/_steps.py +34 -0
- pyfect/sink/_taking.py +183 -0
- pyfect/socket/__init__.py +146 -0
- pyfect/socket/_accepted.py +65 -0
- pyfect/socket/_connection.py +384 -0
- pyfect/socket/_errors.py +135 -0
- pyfect/socket/_loop.py +66 -0
- pyfect/socket/_net.py +281 -0
- pyfect/socket/_socket.py +245 -0
- pyfect/socket/_tls.py +88 -0
- pyfect/socket/_tls_session.py +117 -0
- pyfect/socket/_web_socket.py +555 -0
- pyfect/socket/datagram/__init__.py +110 -0
- pyfect/socket/datagram/_core.py +460 -0
- pyfect/socket/datagram/_errors.py +102 -0
- pyfect/socket/datagram/_live.py +675 -0
- pyfect/socket/datagram/_model.py +217 -0
- pyfect/socket/server.py +258 -0
- pyfect/socket/ws.py +484 -0
- pyfect/sql/__init__.py +60 -0
- pyfect/sql/_loop.py +134 -0
- pyfect/sql/clickhouse/__init__.py +74 -0
- pyfect/sql/clickhouse/_connection.py +237 -0
- pyfect/sql/clickhouse/_rows.py +87 -0
- pyfect/sql/clickhouse/client.py +322 -0
- pyfect/sql/clickhouse/config.py +241 -0
- pyfect/sql/clickhouse/driver.py +434 -0
- pyfect/sql/clickhouse/error.py +106 -0
- pyfect/sql/clickhouse/migrator.py +47 -0
- pyfect/sql/clickhouse/params.py +168 -0
- pyfect/sql/client/__init__.py +35 -0
- pyfect/sql/client/_client.py +309 -0
- pyfect/sql/client/_sqlite_acquirers.py +130 -0
- pyfect/sql/client/_transaction.py +410 -0
- pyfect/sql/connection.py +87 -0
- pyfect/sql/d1/__init__.py +48 -0
- pyfect/sql/d1/api.py +270 -0
- pyfect/sql/d1/client.py +344 -0
- pyfect/sql/d1/config.py +55 -0
- pyfect/sql/error.py +311 -0
- pyfect/sql/libsql/__init__.py +58 -0
- pyfect/sql/libsql/_connection.py +110 -0
- pyfect/sql/libsql/_hrana.py +258 -0
- pyfect/sql/libsql/_local.py +251 -0
- pyfect/sql/libsql/_targets.py +28 -0
- pyfect/sql/libsql/_values.py +46 -0
- pyfect/sql/libsql/client.py +225 -0
- pyfect/sql/libsql/config.py +170 -0
- pyfect/sql/libsql/migrator.py +43 -0
- pyfect/sql/libsql/sdk.py +123 -0
- pyfect/sql/migrator.py +434 -0
- pyfect/sql/model.py +324 -0
- pyfect/sql/mssql/__init__.py +79 -0
- pyfect/sql/mssql/_connection.py +169 -0
- pyfect/sql/mssql/_driver.py +103 -0
- pyfect/sql/mssql/_names.py +9 -0
- pyfect/sql/mssql/_pool.py +125 -0
- pyfect/sql/mssql/_tds.py +447 -0
- pyfect/sql/mssql/_tds_params.py +155 -0
- pyfect/sql/mssql/_worker.py +155 -0
- pyfect/sql/mssql/client.py +344 -0
- pyfect/sql/mssql/config.py +118 -0
- pyfect/sql/mssql/error.py +111 -0
- pyfect/sql/mssql/migrator.py +43 -0
- pyfect/sql/mssql/parameter.py +44 -0
- pyfect/sql/mssql/procedure.py +119 -0
- pyfect/sql/mssql/types.py +137 -0
- pyfect/sql/mysql/__init__.py +60 -0
- pyfect/sql/mysql/_asyncmy.py +168 -0
- pyfect/sql/mysql/_bind.py +76 -0
- pyfect/sql/mysql/_connection.py +156 -0
- pyfect/sql/mysql/_pool.py +63 -0
- pyfect/sql/mysql/_session.py +262 -0
- pyfect/sql/mysql/client.py +244 -0
- pyfect/sql/mysql/config.py +108 -0
- pyfect/sql/mysql/error.py +104 -0
- pyfect/sql/mysql/migrator.py +44 -0
- pyfect/sql/pg/__init__.py +82 -0
- pyfect/sql/pg/_resolve.py +284 -0
- pyfect/sql/pg/_session.py +380 -0
- pyfect/sql/pg/client.py +398 -0
- pyfect/sql/pg/config.py +142 -0
- pyfect/sql/pg/connection.py +585 -0
- pyfect/sql/pg/error.py +131 -0
- pyfect/sql/pg/migrator.py +139 -0
- pyfect/sql/pg/pool.py +265 -0
- pyfect/sql/resolver.py +294 -0
- pyfect/sql/result.py +24 -0
- pyfect/sql/schema.py +116 -0
- pyfect/sql/sqlite/__init__.py +26 -0
- pyfect/sql/sqlite/_connection.py +230 -0
- pyfect/sql/sqlite/_worker.py +71 -0
- pyfect/sql/sqlite/client.py +248 -0
- pyfect/sql/sqlite/migrator.py +43 -0
- pyfect/sql/statement/__init__.py +144 -0
- pyfect/sql/statement/_compiler.py +384 -0
- pyfect/sql/statement/_segments.py +487 -0
- pyfect/sql/statement/_statement.py +526 -0
- pyfect/sql/statement/_transforms.py +93 -0
- pyfect/sql/stream.py +88 -0
- pyfect/stdio.py +206 -0
- pyfect/stream/__init__.py +151 -0
- pyfect/stream/_async_iterable.py +129 -0
- pyfect/stream/_base.py +69 -0
- pyfect/stream/_bridge_methods.py +139 -0
- pyfect/stream/_catch.py +135 -0
- pyfect/stream/_catch_reason.py +558 -0
- pyfect/stream/_channel_methods.py +61 -0
- pyfect/stream/_chunk_methods.py +169 -0
- pyfect/stream/_chunking.py +310 -0
- pyfect/stream/_concat.py +65 -0
- pyfect/stream/_constructors.py +341 -0
- pyfect/stream/_context.py +147 -0
- pyfect/stream/_error_methods.py +254 -0
- pyfect/stream/_execution_plan.py +199 -0
- pyfect/stream/_fanout.py +154 -0
- pyfect/stream/_fanout_methods.py +183 -0
- pyfect/stream/_filter_methods.py +203 -0
- pyfect/stream/_grouping.py +168 -0
- pyfect/stream/_many.py +65 -0
- pyfect/stream/_map_methods.py +233 -0
- pyfect/stream/_mapping.py +126 -0
- pyfect/stream/_run.py +114 -0
- pyfect/stream/_run_methods.py +114 -0
- pyfect/stream/_service_methods.py +36 -0
- pyfect/stream/_sink_methods.py +151 -0
- pyfect/stream/_sinking.py +277 -0
- pyfect/stream/_sources.py +124 -0
- pyfect/stream/_stream.py +162 -0
- pyfect/stream/_taking.py +330 -0
- pyfect/stream/_text.py +43 -0
- pyfect/stream/_timing.py +294 -0
- pyfect/stream/_timing_methods.py +252 -0
- pyfect/stream/_zip_methods.py +279 -0
- pyfect/stream/_zipping.py +213 -0
- pyfect/subscription_ref.py +66 -0
- pyfect/synchronized_ref.py +38 -0
- pyfect/take.py +36 -0
- pyfect/terminal/__init__.py +41 -0
- pyfect/terminal/_keys.py +113 -0
- pyfect/terminal/_live.py +223 -0
- pyfect/terminal/_service.py +73 -0
- pyfect/testing/__init__.py +122 -0
- pyfect/testing/_assertions.py +92 -0
- pyfect/testing/_clock.py +309 -0
- pyfect/testing/_console.py +164 -0
- pyfect/testing/_crypto.py +44 -0
- pyfect/testing/_decorators.py +211 -0
- pyfect/testing/_flaky.py +34 -0
- pyfect/testing/_layers.py +153 -0
- pyfect/testing/_prop.py +255 -0
- pyfect/testing/_runners.py +84 -0
- pyfect/testing/_runtime.py +174 -0
- pyfect/testing/_schema.py +238 -0
- pyfect/testing/plugin.py +59 -0
- pyfect/tracer/__init__.py +107 -0
- pyfect/tracer/_model.py +176 -0
- pyfect/tracer/_native.py +172 -0
- pyfect/tracer/_refs.py +87 -0
- pyfect/tracer/_spans.py +269 -0
- pyfect/transaction.py +340 -0
- pyfect/trie.py +317 -0
- pyfect/tx_chunk.py +127 -0
- pyfect/tx_deferred.py +75 -0
- pyfect/tx_hash_map.py +265 -0
- pyfect/tx_hash_set.py +151 -0
- pyfect/tx_priority_queue.py +224 -0
- pyfect/tx_pubsub.py +194 -0
- pyfect/tx_queue/__init__.py +27 -0
- pyfect/tx_queue/_queue.py +359 -0
- pyfect/tx_queue/_views.py +60 -0
- pyfect/tx_reentrant_lock.py +201 -0
- pyfect/tx_ref.py +79 -0
- pyfect/tx_semaphore.py +136 -0
- pyfect/tx_subscription_ref.py +118 -0
- pyfect/workers/__init__.py +62 -0
- pyfect/workers/_frames.py +82 -0
- pyfect/workers/_mailbox.py +51 -0
- pyfect/workers/_port.py +106 -0
- pyfect/workers/_process.py +155 -0
- pyfect/workers/_thread.py +100 -0
- pyfect/workers/python_worker.py +133 -0
- pyfect/workers/python_worker_runner.py +112 -0
- pyfect/workers/transferable.py +103 -0
- pyfect/workers/worker.py +273 -0
- pyfect/workers/worker_error.py +86 -0
- pyfect/workers/worker_runner.py +85 -0
- pyfect/workflow/__init__.py +78 -0
- pyfect/workflow/_crypto.py +23 -0
- pyfect/workflow/_encoded.py +381 -0
- pyfect/workflow/_memory.py +424 -0
- pyfect/workflow/_result.py +43 -0
- pyfect/workflow/_services.py +241 -0
- pyfect/workflow/_suspension.py +153 -0
- pyfect/workflow/activity.py +427 -0
- pyfect/workflow/durable_clock.py +72 -0
- pyfect/workflow/durable_deferred.py +403 -0
- pyfect/workflow/durable_queue.py +259 -0
- pyfect/workflow/workflow.py +509 -0
- pyfect/workflow/workflow_engine.py +42 -0
- pyfect/workflow/workflow_proxy.py +161 -0
- pyfect/workflow/workflow_proxy_server.py +146 -0
- pyfect_dev-0.1.0.dist-info/METADATA +296 -0
- pyfect_dev-0.1.0.dist-info/RECORD +1265 -0
- pyfect_dev-0.1.0.dist-info/WHEEL +4 -0
- pyfect_dev-0.1.0.dist-info/entry_points.txt +3 -0
- pyfect_dev-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
"""The transports of an RPC server: sockets (TCP, WebSocket) and HTTP
|
|
2
|
+
(Effect's ``makeSocketProtocol`` and ``makeProtocolWithHttpEffect``)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Literal, Never, cast
|
|
9
|
+
|
|
10
|
+
from pyfect._core import Effect
|
|
11
|
+
from pyfect.effect import fail, gen, succeed, suspend
|
|
12
|
+
from pyfect.errors import Done
|
|
13
|
+
from pyfect.http.origin import origin_allowed
|
|
14
|
+
from pyfect.http.server_request import HttpServerRequest
|
|
15
|
+
from pyfect.http.server_response import HttpServerResponse, empty, text
|
|
16
|
+
from pyfect.http.server_response import bytes_ as bytes_response
|
|
17
|
+
from pyfect.http.server_response import stream as stream_response
|
|
18
|
+
from pyfect.queue import Queue
|
|
19
|
+
from pyfect.rpc.message import RequestId, response_defect_encoded
|
|
20
|
+
from pyfect.rpc.serialization import (
|
|
21
|
+
Frame,
|
|
22
|
+
MaxBufferSizeExceeded,
|
|
23
|
+
Parser,
|
|
24
|
+
RpcSerialization,
|
|
25
|
+
RpcSerializationService,
|
|
26
|
+
encode_text,
|
|
27
|
+
is_binary,
|
|
28
|
+
)
|
|
29
|
+
from pyfect.rpc.server._protocol import (
|
|
30
|
+
Message,
|
|
31
|
+
ProtocolParts,
|
|
32
|
+
ServerProtocolService,
|
|
33
|
+
WriteRequest,
|
|
34
|
+
with_run,
|
|
35
|
+
)
|
|
36
|
+
from pyfect.rpc.server._serialized import encode_defect_for
|
|
37
|
+
from pyfect.scope import Scope, fork_scoped, scoped
|
|
38
|
+
from pyfect.socket import CloseEvent, SocketCloseError, SocketError, SocketService, Writer
|
|
39
|
+
from pyfect.socket.server import SocketServer
|
|
40
|
+
from pyfect.stream import Stream, from_iterable, from_queue
|
|
41
|
+
|
|
42
|
+
_VOID: Effect[None] = succeed(None)
|
|
43
|
+
|
|
44
|
+
type BufferSize = int | Literal["unbounded"]
|
|
45
|
+
type RequestHeaders = tuple[tuple[str, str], ...]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _with_headers(message: Message, headers: RequestHeaders) -> Message:
|
|
49
|
+
"""A request with the transport's headers added to its own. A name the
|
|
50
|
+
transport carries replaces the request's: a proxy vouches for the
|
|
51
|
+
transport's headers, while the message is the caller's (Effect lets the
|
|
52
|
+
message's win)."""
|
|
53
|
+
if message.get("_tag") != "Request" or not headers:
|
|
54
|
+
return message
|
|
55
|
+
carried = {name.lower() for name, _ in headers}
|
|
56
|
+
own = [
|
|
57
|
+
pair
|
|
58
|
+
for pair in cast("list[Any]", list(message.get("headers") or ()))
|
|
59
|
+
if not (_is_pair(pair) and str(pair[0]).lower() in carried)
|
|
60
|
+
]
|
|
61
|
+
return {**message, "headers": [*own, *headers]}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _is_pair(pair: object) -> bool:
|
|
65
|
+
return isinstance(pair, list | tuple) and len(cast("tuple[object, ...]", pair)) == 2
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# --- sockets ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass(frozen=True, slots=True)
|
|
72
|
+
class SocketProtocol:
|
|
73
|
+
"""A server protocol over sockets: ``on_socket`` serves one connection
|
|
74
|
+
(until it closes); the ``protocol`` is the server's side."""
|
|
75
|
+
|
|
76
|
+
protocol: ServerProtocolService
|
|
77
|
+
on_socket: Callable[[SocketService, RequestHeaders], Effect[None, Never, Scope]]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def make_socket_protocol() -> Effect[SocketProtocol, Never, RpcSerialization]:
|
|
81
|
+
@gen
|
|
82
|
+
def make():
|
|
83
|
+
serialization = yield from RpcSerialization()
|
|
84
|
+
encode_defect = encode_defect_for(serialization.codec_for)
|
|
85
|
+
disconnects = yield from Queue[int, Never].unbounded()
|
|
86
|
+
clients: dict[int, Callable[[Message], Effect[None]]] = {}
|
|
87
|
+
ids = iter(range(1 << 62))
|
|
88
|
+
writes: list[WriteRequest] = []
|
|
89
|
+
|
|
90
|
+
def on_socket(socket: SocketService, headers: RequestHeaders) -> Effect[None, Never, Scope]:
|
|
91
|
+
@gen
|
|
92
|
+
def serve():
|
|
93
|
+
scope = yield from Scope()
|
|
94
|
+
parser = serialization.make_unsafe()
|
|
95
|
+
client_id = next(ids)
|
|
96
|
+
|
|
97
|
+
def leave(_: object) -> Effect[None]:
|
|
98
|
+
clients.pop(client_id, None)
|
|
99
|
+
return disconnects.offer(client_id).as_void()
|
|
100
|
+
|
|
101
|
+
yield from scope.add_finalizer(leave)
|
|
102
|
+
writer = yield from socket.writer
|
|
103
|
+
clients[client_id] = lambda response: _socket_write(
|
|
104
|
+
writer, parser, encode_defect, response
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def process(data: Frame) -> Effect[None, SocketError]:
|
|
108
|
+
try:
|
|
109
|
+
decoded = parser.decode(data)
|
|
110
|
+
except MaxBufferSizeExceeded as cause:
|
|
111
|
+
return writer.write(CloseEvent(1009, str(cause)))
|
|
112
|
+
except Exception as cause:
|
|
113
|
+
defect = parser.encode(response_defect_encoded(encode_defect(cause)))
|
|
114
|
+
return _VOID if defect is None else writer.write(defect)
|
|
115
|
+
|
|
116
|
+
@gen
|
|
117
|
+
def each():
|
|
118
|
+
for message in decoded:
|
|
119
|
+
yield from writes[0](
|
|
120
|
+
client_id, _with_headers(cast("Message", message), headers)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return each
|
|
124
|
+
|
|
125
|
+
@gen
|
|
126
|
+
def read():
|
|
127
|
+
reader = yield from socket.reader
|
|
128
|
+
while True:
|
|
129
|
+
frames = yield from reader.pull
|
|
130
|
+
for frame in frames:
|
|
131
|
+
yield from process(frame)
|
|
132
|
+
|
|
133
|
+
def closed(error: SocketError) -> Effect[None, SocketError]:
|
|
134
|
+
return _VOID if isinstance(error.reason, SocketCloseError) else fail(error)
|
|
135
|
+
|
|
136
|
+
yield from read.catch_all(closed).or_die()
|
|
137
|
+
|
|
138
|
+
return serve
|
|
139
|
+
|
|
140
|
+
def send(client_id: int, response: Message) -> Effect[None]:
|
|
141
|
+
write = clients.get(client_id)
|
|
142
|
+
return _VOID if write is None else write(response)
|
|
143
|
+
|
|
144
|
+
def parts(write: WriteRequest) -> Effect[ProtocolParts]:
|
|
145
|
+
writes.append(write)
|
|
146
|
+
return succeed(
|
|
147
|
+
ProtocolParts(
|
|
148
|
+
disconnects=disconnects,
|
|
149
|
+
send=send,
|
|
150
|
+
end=lambda _: _VOID,
|
|
151
|
+
supports_ack=True,
|
|
152
|
+
supports_span_propagation=True,
|
|
153
|
+
codec_for=serialization.codec_for,
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
protocol = yield from with_run(parts)
|
|
158
|
+
return SocketProtocol(protocol, on_socket)
|
|
159
|
+
|
|
160
|
+
return make
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _socket_write(
|
|
164
|
+
writer: Writer, parser: Parser, encode_defect: Callable[[object], object], response: Message
|
|
165
|
+
) -> Effect[None]:
|
|
166
|
+
try:
|
|
167
|
+
encoded = parser.encode(response)
|
|
168
|
+
except Exception as cause:
|
|
169
|
+
encoded = parser.encode(response_defect_encoded(encode_defect(cause)))
|
|
170
|
+
if encoded is None:
|
|
171
|
+
return _VOID
|
|
172
|
+
return writer.write(encoded).or_die()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def make_protocol_socket_server() -> Effect[
|
|
176
|
+
ServerProtocolService, Never, RpcSerialization | SocketServer | Scope
|
|
177
|
+
]:
|
|
178
|
+
"""A server protocol over the connections of the ``SocketServer``."""
|
|
179
|
+
|
|
180
|
+
@gen
|
|
181
|
+
def make():
|
|
182
|
+
server = yield from SocketServer()
|
|
183
|
+
made = yield from make_socket_protocol()
|
|
184
|
+
yield from fork_scoped(server.run(lambda socket: scoped(made.on_socket(socket, ()))))
|
|
185
|
+
return made.protocol
|
|
186
|
+
|
|
187
|
+
return make
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# --- WebSocket and HTTP ----------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
type HttpEffect = Effect[HttpServerResponse, Never, Scope | HttpServerRequest]
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@dataclass(frozen=True, slots=True)
|
|
197
|
+
class HttpProtocol:
|
|
198
|
+
"""A server protocol and the HTTP handler feeding it."""
|
|
199
|
+
|
|
200
|
+
protocol: ServerProtocolService
|
|
201
|
+
http_effect: HttpEffect
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _request_headers(request: Any) -> RequestHeaders:
|
|
205
|
+
return tuple((k, v) for k, v in request.headers.items())
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def make_protocol_with_http_effect_websocket() -> Effect[HttpProtocol, Never, RpcSerialization]:
|
|
209
|
+
"""A server protocol over WebSockets, upgraded by its HTTP handler."""
|
|
210
|
+
|
|
211
|
+
@gen
|
|
212
|
+
def make():
|
|
213
|
+
made = yield from make_socket_protocol()
|
|
214
|
+
|
|
215
|
+
@gen
|
|
216
|
+
def handle():
|
|
217
|
+
request = yield from HttpServerRequest()
|
|
218
|
+
if not (yield from origin_allowed(request)):
|
|
219
|
+
return empty(status=403)
|
|
220
|
+
socket = yield from request.upgrade.or_die()
|
|
221
|
+
yield from made.on_socket(socket, _request_headers(request))
|
|
222
|
+
return empty()
|
|
223
|
+
|
|
224
|
+
return HttpProtocol(made.protocol, handle)
|
|
225
|
+
|
|
226
|
+
return make
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _read_frame(request: Any, binary: bool) -> Effect[Frame]:
|
|
230
|
+
return request.bytes_.or_die() if binary else request.text.or_die()
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@dataclass(slots=True, eq=False)
|
|
234
|
+
class _HttpClient:
|
|
235
|
+
write: Callable[[Message], Effect[None]]
|
|
236
|
+
end: Effect[None]
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def make_protocol_with_http_effect(
|
|
240
|
+
*, stream_buffer_size: BufferSize = 16
|
|
241
|
+
) -> Effect[HttpProtocol, Never, RpcSerialization]:
|
|
242
|
+
"""A server protocol over HTTP POSTs: each request body is a client's
|
|
243
|
+
messages; the response holds its responses (streamed as they come for
|
|
244
|
+
a framed serialization)."""
|
|
245
|
+
|
|
246
|
+
@gen
|
|
247
|
+
def make():
|
|
248
|
+
serialization = yield from RpcSerialization()
|
|
249
|
+
encode_defect = encode_defect_for(serialization.codec_for)
|
|
250
|
+
disconnects = yield from Queue[int, Never].unbounded()
|
|
251
|
+
clients: dict[int, _HttpClient] = {}
|
|
252
|
+
ids = iter(range(1 << 62))
|
|
253
|
+
writes: list[WriteRequest] = []
|
|
254
|
+
|
|
255
|
+
def parts(write: WriteRequest) -> Effect[ProtocolParts]:
|
|
256
|
+
writes.append(write)
|
|
257
|
+
|
|
258
|
+
def send(client_id: int, response: Message) -> Effect[None]:
|
|
259
|
+
client = clients.get(client_id)
|
|
260
|
+
return _VOID if client is None else client.write(response)
|
|
261
|
+
|
|
262
|
+
def end(client_id: int) -> Effect[None]:
|
|
263
|
+
client = clients.get(client_id)
|
|
264
|
+
return _VOID if client is None else client.end
|
|
265
|
+
|
|
266
|
+
return succeed(
|
|
267
|
+
ProtocolParts(
|
|
268
|
+
disconnects=disconnects,
|
|
269
|
+
send=send,
|
|
270
|
+
end=end,
|
|
271
|
+
supports_ack=False,
|
|
272
|
+
supports_span_propagation=False,
|
|
273
|
+
codec_for=serialization.codec_for,
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
protocol = yield from with_run(parts)
|
|
278
|
+
handler = _HttpHandler(
|
|
279
|
+
serialization,
|
|
280
|
+
encode_defect=encode_defect,
|
|
281
|
+
disconnects=disconnects,
|
|
282
|
+
clients=clients,
|
|
283
|
+
ids=ids,
|
|
284
|
+
writes=writes,
|
|
285
|
+
buffer=stream_buffer_size,
|
|
286
|
+
)
|
|
287
|
+
return HttpProtocol(protocol, handler.handle())
|
|
288
|
+
|
|
289
|
+
return make
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class _HttpHandler:
|
|
293
|
+
__slots__ = (
|
|
294
|
+
"_buffer",
|
|
295
|
+
"_clients",
|
|
296
|
+
"_disconnects",
|
|
297
|
+
"_encode_defect",
|
|
298
|
+
"_ids",
|
|
299
|
+
"_serialization",
|
|
300
|
+
"_writes",
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
def __init__(
|
|
304
|
+
self,
|
|
305
|
+
serialization: RpcSerializationService,
|
|
306
|
+
*,
|
|
307
|
+
encode_defect: Callable[[object], object],
|
|
308
|
+
disconnects: Queue[int, Never],
|
|
309
|
+
clients: dict[int, _HttpClient],
|
|
310
|
+
ids: Any,
|
|
311
|
+
writes: list[WriteRequest],
|
|
312
|
+
buffer: BufferSize,
|
|
313
|
+
) -> None:
|
|
314
|
+
self._serialization = serialization
|
|
315
|
+
self._encode_defect = encode_defect
|
|
316
|
+
self._disconnects = disconnects
|
|
317
|
+
self._clients = clients
|
|
318
|
+
self._ids = ids
|
|
319
|
+
self._writes = writes
|
|
320
|
+
self._buffer = buffer
|
|
321
|
+
|
|
322
|
+
def handle(self) -> HttpEffect:
|
|
323
|
+
serialization = self._serialization
|
|
324
|
+
framed = serialization.includes_framing
|
|
325
|
+
|
|
326
|
+
@gen
|
|
327
|
+
def run():
|
|
328
|
+
request = yield from HttpServerRequest()
|
|
329
|
+
if not (yield from origin_allowed(request)):
|
|
330
|
+
return empty(status=403)
|
|
331
|
+
scope = yield from Scope()
|
|
332
|
+
binary = is_binary(serialization)
|
|
333
|
+
data = yield from _read_frame(request, binary)
|
|
334
|
+
client_id: int = next(self._ids)
|
|
335
|
+
capacity = self._buffer if framed and isinstance(self._buffer, int) else None
|
|
336
|
+
queue = yield from Queue[Any, Done].make(capacity)
|
|
337
|
+
parser = serialization.make_unsafe()
|
|
338
|
+
request_ids: list[RequestId] = []
|
|
339
|
+
client = _HttpClient(self._writer(queue, parser), queue.end().as_void())
|
|
340
|
+
|
|
341
|
+
def leave(_: object) -> Effect[None]:
|
|
342
|
+
self._clients.pop(client_id, None)
|
|
343
|
+
self._disconnects.offer_unsafe(client_id)
|
|
344
|
+
if queue.is_done_unsafe():
|
|
345
|
+
return _VOID
|
|
346
|
+
write = self._writes[0]
|
|
347
|
+
|
|
348
|
+
@gen
|
|
349
|
+
def interrupt_all():
|
|
350
|
+
yield from queue.shutdown()
|
|
351
|
+
for request_id in request_ids:
|
|
352
|
+
yield from write(client_id, {"_tag": "Interrupt", "requestId": request_id})
|
|
353
|
+
|
|
354
|
+
return interrupt_all
|
|
355
|
+
|
|
356
|
+
yield from scope.add_finalizer(leave)
|
|
357
|
+
self._clients[client_id] = client
|
|
358
|
+
headers = _request_headers(request)
|
|
359
|
+
try:
|
|
360
|
+
decoded = [cast("Message", m) for m in parser.decode(data)]
|
|
361
|
+
except Exception as cause:
|
|
362
|
+
decoded = []
|
|
363
|
+
yield from client.write(response_defect_encoded(self._encode_defect(cause)))
|
|
364
|
+
for message in decoded:
|
|
365
|
+
if message.get("_tag") == "Request":
|
|
366
|
+
request_ids.append(message["id"])
|
|
367
|
+
yield from self._writes[0](client_id, _with_headers(message, headers))
|
|
368
|
+
yield from self._writes[0](client_id, {"_tag": "Eof"})
|
|
369
|
+
content_type = serialization.content_type
|
|
370
|
+
if not framed:
|
|
371
|
+
collected: Effect[list[Any], Never] = queue.collect()
|
|
372
|
+
responses: list[Any] = yield from collected.interruptible()
|
|
373
|
+
encoded = parser.encode(responses) if responses else None
|
|
374
|
+
return text("" if encoded is None else _as_text(encoded), content_type=content_type)
|
|
375
|
+
take_all: Effect[list[Any], Never] = queue.take_all().catch_all(
|
|
376
|
+
lambda _: succeed(list[Any]())
|
|
377
|
+
)
|
|
378
|
+
initial: list[Any] = yield from take_all.interruptible()
|
|
379
|
+
chunks = [encode_text(c) for c in initial]
|
|
380
|
+
if queue.is_done_unsafe():
|
|
381
|
+
joined = b"".join(chunks)
|
|
382
|
+
if binary:
|
|
383
|
+
return bytes_response(joined, content_type=content_type)
|
|
384
|
+
return text(joined.decode(), content_type=content_type)
|
|
385
|
+
body: Stream[bytes, Never] = from_iterable(chunks).concat(
|
|
386
|
+
from_queue(queue).map(encode_text)
|
|
387
|
+
)
|
|
388
|
+
return stream_response(body, content_type=content_type)
|
|
389
|
+
|
|
390
|
+
return run
|
|
391
|
+
|
|
392
|
+
def _writer(self, queue: Queue[Any, Done], parser: Parser) -> Callable[[Message], Effect[None]]:
|
|
393
|
+
if not self._serialization.includes_framing:
|
|
394
|
+
|
|
395
|
+
def buffered(response: Message) -> Effect[None]:
|
|
396
|
+
if response.get("_tag") == "Request" and response.get("isNotification"):
|
|
397
|
+
return _VOID
|
|
398
|
+
return queue.offer(response).as_void()
|
|
399
|
+
|
|
400
|
+
return buffered
|
|
401
|
+
|
|
402
|
+
def framed(response: Message) -> Effect[None]:
|
|
403
|
+
def body() -> Effect[None]:
|
|
404
|
+
try:
|
|
405
|
+
encoded = parser.encode(response)
|
|
406
|
+
except Exception as cause:
|
|
407
|
+
encoded = parser.encode(response_defect_encoded(self._encode_defect(cause)))
|
|
408
|
+
return _VOID if encoded is None else queue.offer(encoded).as_void()
|
|
409
|
+
|
|
410
|
+
return suspend(body)
|
|
411
|
+
|
|
412
|
+
return framed
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _as_text(frame: Frame) -> str:
|
|
416
|
+
return frame if isinstance(frame, str) else frame.decode()
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
__all__ = [
|
|
420
|
+
"HttpEffect",
|
|
421
|
+
"HttpProtocol",
|
|
422
|
+
"SocketProtocol",
|
|
423
|
+
"make_protocol_socket_server",
|
|
424
|
+
"make_protocol_with_http_effect",
|
|
425
|
+
"make_protocol_with_http_effect_websocket",
|
|
426
|
+
"make_socket_protocol",
|
|
427
|
+
]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""The worker transport of an RPC server (Effect's
|
|
2
|
+
``makeProtocolWorkerRunner``): the server runs inside a worker and serves
|
|
3
|
+
the parent's requests through the ``WorkerRunner``."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Any, cast
|
|
8
|
+
|
|
9
|
+
from pyfect import schema as S
|
|
10
|
+
from pyfect._core import Effect
|
|
11
|
+
from pyfect.deferred import Deferred
|
|
12
|
+
from pyfect.effect import current_fiber, gen, sync
|
|
13
|
+
from pyfect.layer import Layer
|
|
14
|
+
from pyfect.layer import effect as layer_effect
|
|
15
|
+
from pyfect.logger import log_error
|
|
16
|
+
from pyfect.option import Option, Some
|
|
17
|
+
from pyfect.queue import Queue
|
|
18
|
+
from pyfect.rpc.serialization import CodecFor
|
|
19
|
+
from pyfect.rpc.server._protocol import (
|
|
20
|
+
Message,
|
|
21
|
+
ProtocolParts,
|
|
22
|
+
ServerProtocol,
|
|
23
|
+
ServerProtocolService,
|
|
24
|
+
WriteRequest,
|
|
25
|
+
with_run,
|
|
26
|
+
)
|
|
27
|
+
from pyfect.scheduler import CurrentScheduler
|
|
28
|
+
from pyfect.scope import Scope, fork_scoped
|
|
29
|
+
from pyfect.workers.worker_error import WorkerError
|
|
30
|
+
from pyfect.workers.worker_runner import WorkerRunnerPlatform
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _codec_for(schema: Any) -> Any:
|
|
34
|
+
# Worker messages are values, so they do not depend on RpcSerialization.
|
|
35
|
+
return S.to_codec_json(schema)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _some(value: object) -> Option[object]:
|
|
39
|
+
return Some(value)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def make_protocol_worker_runner() -> Effect[
|
|
43
|
+
ServerProtocolService, WorkerError, WorkerRunnerPlatform | Scope
|
|
44
|
+
]:
|
|
45
|
+
"""A server protocol over the ``WorkerRunner`` of the worker this runs
|
|
46
|
+
in. When the parent closes the worker, the fiber that made the
|
|
47
|
+
protocol (a ``layer.launch``) is interrupted, so the worker's program
|
|
48
|
+
ends."""
|
|
49
|
+
|
|
50
|
+
def parts(write: WriteRequest) -> Effect[ProtocolParts, WorkerError, Any]:
|
|
51
|
+
@gen
|
|
52
|
+
def make():
|
|
53
|
+
fiber = yield from current_fiber()
|
|
54
|
+
scheduler = yield from CurrentScheduler()
|
|
55
|
+
platform = yield from WorkerRunnerPlatform()
|
|
56
|
+
backing = yield from platform.start()
|
|
57
|
+
initial = Deferred[object]()
|
|
58
|
+
client_ids: set[int] = set()
|
|
59
|
+
disconnects = yield from Queue[int].unbounded()
|
|
60
|
+
|
|
61
|
+
def on_message(client_id: int, message: Message) -> Effect[Any]:
|
|
62
|
+
client_ids.add(client_id)
|
|
63
|
+
if message.get("_tag") == "InitialMessage":
|
|
64
|
+
return initial.succeed(message.get("value"))
|
|
65
|
+
return write(client_id, message)
|
|
66
|
+
|
|
67
|
+
def end_program(_: object) -> Effect[None]:
|
|
68
|
+
return sync(lambda: scheduler.schedule(lambda: fiber.interrupt_as_unsafe(fiber.id)))
|
|
69
|
+
|
|
70
|
+
yield from fork_scoped(
|
|
71
|
+
backing.run(on_message).tap_cause(log_error).on_exit(end_program)
|
|
72
|
+
)
|
|
73
|
+
if backing.disconnects is not None:
|
|
74
|
+
dequeue = backing.disconnects
|
|
75
|
+
|
|
76
|
+
def left(client_id: int) -> Effect[None]:
|
|
77
|
+
client_ids.discard(client_id)
|
|
78
|
+
return disconnects.offer(client_id).as_void()
|
|
79
|
+
|
|
80
|
+
yield from fork_scoped(dequeue.take().flat_map(left).forever())
|
|
81
|
+
|
|
82
|
+
def send(client_id: int, response: Message) -> Effect[None]:
|
|
83
|
+
return backing.send(client_id, response)
|
|
84
|
+
|
|
85
|
+
return ProtocolParts(
|
|
86
|
+
disconnects=disconnects,
|
|
87
|
+
send=send,
|
|
88
|
+
end=lambda _: sync(lambda: None),
|
|
89
|
+
supports_ack=True,
|
|
90
|
+
supports_span_propagation=True,
|
|
91
|
+
codec_for=cast("CodecFor", _codec_for),
|
|
92
|
+
initial_message=initial.wait().map(_some),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return make
|
|
96
|
+
|
|
97
|
+
return cast(
|
|
98
|
+
"Effect[ServerProtocolService, WorkerError, WorkerRunnerPlatform | Scope]",
|
|
99
|
+
with_run(parts),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
layer_protocol_worker_runner: Layer[ServerProtocol, WorkerError, WorkerRunnerPlatform] = (
|
|
104
|
+
layer_effect(ServerProtocol, make_protocol_worker_runner())
|
|
105
|
+
)
|
|
106
|
+
"""A server protocol over the current ``WorkerRunnerPlatform``."""
|
|
107
|
+
|
|
108
|
+
__all__ = ["layer_protocol_worker_runner", "make_protocol_worker_runner"]
|
pyfect/rpc/test.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""An in-memory client of a group's handlers (Effect's ``RpcTest``).
|
|
2
|
+
|
|
3
|
+
``make_client(Group, layer=GroupLive)`` builds the handlers' layer into the
|
|
4
|
+
caller's ``Scope``, so the handlers' resources (a database, forked workers)
|
|
5
|
+
stay open while the client is used::
|
|
6
|
+
|
|
7
|
+
client = yield from rpc_test.make_client(Todos, layer=TodosLive)
|
|
8
|
+
|
|
9
|
+
Without ``layer`` the handlers come from the context, which must then
|
|
10
|
+
outlive the client: a layer provided to ``make_client(...)`` alone is
|
|
11
|
+
released once the client is made, as in Effect.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any, Never, overload
|
|
17
|
+
|
|
18
|
+
from pyfect._core import Effect
|
|
19
|
+
from pyfect._layer_core import Layer
|
|
20
|
+
from pyfect._layer_for_life import with_layer
|
|
21
|
+
from pyfect.effect import gen
|
|
22
|
+
from pyfect.rpc.client._core import ClientCore, OnFromClient
|
|
23
|
+
from pyfect.rpc.client._core import make_no_serialization as make_client_core
|
|
24
|
+
from pyfect.rpc.group import HandlersFor, RpcGroup
|
|
25
|
+
from pyfect.rpc.message import FromServer
|
|
26
|
+
from pyfect.rpc.server._core import ServerOptions
|
|
27
|
+
from pyfect.rpc.server._core import make_no_serialization as make_server_core
|
|
28
|
+
from pyfect.scope import Scope
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@overload
|
|
32
|
+
def make_client[G: RpcGroup](
|
|
33
|
+
group: type[G], *, disable_fatal_defects: bool = False
|
|
34
|
+
) -> Effect[G, Never, Scope | HandlersFor[G]]: ...
|
|
35
|
+
@overload
|
|
36
|
+
def make_client[G: RpcGroup, E, R](
|
|
37
|
+
group: type[G], *, layer: Layer[HandlersFor[G], E, R], disable_fatal_defects: bool = False
|
|
38
|
+
) -> Effect[G, E, R | Scope]: ...
|
|
39
|
+
def make_client(
|
|
40
|
+
group: type[RpcGroup],
|
|
41
|
+
*,
|
|
42
|
+
layer: Layer[Any, Any, Any] | None = None,
|
|
43
|
+
disable_fatal_defects: bool = False,
|
|
44
|
+
) -> Effect[Any, Any, Any]:
|
|
45
|
+
"""A client of ``group`` calling its handlers directly, without
|
|
46
|
+
serialization. The handlers (and middleware) are ``layer``, built into
|
|
47
|
+
the caller's ``Scope``, or else come from the context; the client is
|
|
48
|
+
valid until that scope closes."""
|
|
49
|
+
return with_layer(lambda: _make_client(group, disable_fatal_defects), layer)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _make_client(group: type[RpcGroup], disable_fatal_defects: bool) -> Effect[Any, Never, Any]:
|
|
53
|
+
cores: list[ClientCore] = []
|
|
54
|
+
|
|
55
|
+
def on_from_server(response: FromServer) -> Effect[None]:
|
|
56
|
+
return cores[0].write(response)
|
|
57
|
+
|
|
58
|
+
@gen
|
|
59
|
+
def make():
|
|
60
|
+
server = yield from make_server_core(
|
|
61
|
+
group, on_from_server, ServerOptions(disable_fatal_defects=disable_fatal_defects)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def on_from_client(sent: OnFromClient) -> Effect[None]:
|
|
65
|
+
return server.write(0, sent.message)
|
|
66
|
+
|
|
67
|
+
client, core = yield from make_client_core(group, on_from_client)
|
|
68
|
+
cores.append(core)
|
|
69
|
+
return client
|
|
70
|
+
|
|
71
|
+
return make
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
__all__ = ["make_client"]
|
pyfect/rpc/worker.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""The initial message of RPC workers (Effect's ``RpcWorker``).
|
|
2
|
+
|
|
3
|
+
A worker client (``rpc.client.layer_protocol_worker``) can send each
|
|
4
|
+
worker one value before any request: provide ``InitialMessage`` with
|
|
5
|
+
``layer_initial_message(schema, build)``, and read it in the worker with
|
|
6
|
+
``initial_message(schema)``, which waits for it.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from pyfect import schema as S
|
|
14
|
+
from pyfect._core import Effect
|
|
15
|
+
from pyfect._effect_args import require_effect
|
|
16
|
+
from pyfect.context import Context, Tag
|
|
17
|
+
from pyfect.effect import context, fail, provide_context, provide_service, succeed
|
|
18
|
+
from pyfect.errors import NoSuchElementException
|
|
19
|
+
from pyfect.layer import Layer
|
|
20
|
+
from pyfect.layer import effect as layer_effect
|
|
21
|
+
from pyfect.option import Option
|
|
22
|
+
from pyfect.rpc.server._protocol import ServerProtocol
|
|
23
|
+
from pyfect.workers.transferable import Collector, make_collector_unsafe
|
|
24
|
+
|
|
25
|
+
type Encoded = tuple[object, tuple[object, ...]]
|
|
26
|
+
"""An encoded initial message and the values to transfer with it."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class InitialMessage(Tag[Effect[Encoded]], key="effect/rpc/RpcWorker/InitialMessage"):
|
|
30
|
+
"""Makes the value a worker client sends each worker first."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def make_initial_message[A, E = Any, R = Any](
|
|
34
|
+
schema: S.Codec[A, Any, Any, Any], eff: Effect[A, E, R]
|
|
35
|
+
) -> Effect[Encoded, E | S.SchemaError, R]:
|
|
36
|
+
"""Run ``eff`` and encode its value with ``schema``'s JSON codec,
|
|
37
|
+
collecting its transferables."""
|
|
38
|
+
require_effect(eff, "eff")
|
|
39
|
+
encode = S.encode_effect(S.to_codec_json(schema))
|
|
40
|
+
|
|
41
|
+
def encoded(value: A) -> Effect[Encoded, S.SchemaError, Any]:
|
|
42
|
+
collector = make_collector_unsafe()
|
|
43
|
+
return (
|
|
44
|
+
encode(value)
|
|
45
|
+
.pipe(provide_service(Collector, collector))
|
|
46
|
+
.map(lambda data: (data, tuple(collector.clear_unsafe())))
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return eff.flat_map(encoded)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def layer_initial_message[A, R = Any](
|
|
53
|
+
schema: S.Codec[A, Any, Any, Any], build: Effect[A, Any, R]
|
|
54
|
+
) -> Layer[InitialMessage, Any, R]:
|
|
55
|
+
"""Provide ``InitialMessage`` from ``build``, run with the layer's
|
|
56
|
+
services each time a worker spawns; an encoding failure is a defect."""
|
|
57
|
+
require_effect(build, "build")
|
|
58
|
+
|
|
59
|
+
def made(services: Context[Any]) -> Effect[Encoded]:
|
|
60
|
+
return make_initial_message(schema, build).or_die().pipe(provide_context(services))
|
|
61
|
+
|
|
62
|
+
return layer_effect(InitialMessage, context().map(made))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _from_option(found: Option[object]) -> Effect[object, NoSuchElementException]:
|
|
66
|
+
if found is None:
|
|
67
|
+
return fail(NoSuchElementException())
|
|
68
|
+
return succeed(found.value)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def initial_message[A](
|
|
72
|
+
schema: S.Codec[A, Any, Any, Any],
|
|
73
|
+
) -> Effect[A, NoSuchElementException | S.SchemaError, ServerProtocol]:
|
|
74
|
+
"""The initial message the client sent this worker, decoded with
|
|
75
|
+
``schema``'s JSON codec; fails when the protocol has none."""
|
|
76
|
+
decode = S.decode_unknown_effect(S.to_codec_json(schema))
|
|
77
|
+
return (
|
|
78
|
+
ServerProtocol()
|
|
79
|
+
.flat_map(lambda protocol: protocol.initial_message)
|
|
80
|
+
.flat_map(_from_option)
|
|
81
|
+
.flat_map(decode)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
__all__ = [
|
|
86
|
+
"Encoded",
|
|
87
|
+
"InitialMessage",
|
|
88
|
+
"NoSuchElementException",
|
|
89
|
+
"initial_message",
|
|
90
|
+
"layer_initial_message",
|
|
91
|
+
"make_initial_message",
|
|
92
|
+
]
|