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,225 @@
|
|
|
1
|
+
"""Serving one connection: read a head, hand the request to the app, write
|
|
2
|
+
its response, and keep the connection for the next request unless it must
|
|
3
|
+
close (a ``connection: close``, a body too long to drain, a broken
|
|
4
|
+
response, a shutdown)."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from http import HTTPStatus
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from pyfect._core import Effect
|
|
15
|
+
from pyfect._fiber import Fiber
|
|
16
|
+
from pyfect.duration import Seconds
|
|
17
|
+
from pyfect.effect import gen, succeed, sync, void
|
|
18
|
+
from pyfect.http._authority import LOCALHOST, absolute_url
|
|
19
|
+
from pyfect.http.headers import Headers
|
|
20
|
+
from pyfect.http.live_server._exchange import MAX_DRAIN_BYTES, Exchange
|
|
21
|
+
from pyfect.http.live_server._head import HeadError, RequestHead, parse_head
|
|
22
|
+
from pyfect.http.live_server._inbound import BareLineFeed, HeadTooLarge, Inbound
|
|
23
|
+
from pyfect.http.live_server._listener import ConnectionState, Listener
|
|
24
|
+
from pyfect.http.live_server._timers import timeout_option
|
|
25
|
+
from pyfect.http.live_server._upgrade import upgrade
|
|
26
|
+
from pyfect.http.server_error import CLIENT_ABORT_ANNOTATION
|
|
27
|
+
from pyfect.http.server_request import RequestProtocol, SourceRequest
|
|
28
|
+
from pyfect.option import Option
|
|
29
|
+
from pyfect.socket._connection import Connection
|
|
30
|
+
from pyfect.socket._errors import SocketError
|
|
31
|
+
|
|
32
|
+
MAX_HEAD_SIZE = 16 * 1024
|
|
33
|
+
"""Node's ``maxHeaderSize``: a longer head is answered with 431."""
|
|
34
|
+
|
|
35
|
+
CLOSE_GRACE_PERIOD = Seconds(0.5)
|
|
36
|
+
"""How long a closing connection waits for the client to stop sending
|
|
37
|
+
before it is torn down (so the response is not lost to a reset)."""
|
|
38
|
+
|
|
39
|
+
type Handle = Callable[[SourceRequest, Exchange], Effect[None]]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True, slots=True)
|
|
43
|
+
class Timeouts:
|
|
44
|
+
"""Node's ``keepAliveTimeout`` (an idle connection between requests is
|
|
45
|
+
closed), ``headersTimeout`` (a head must arrive within it once it is
|
|
46
|
+
awaited, or started after a keep-alive wait; a started one is answered
|
|
47
|
+
with 408) and ``requestTimeout`` (the body must have arrived within it
|
|
48
|
+
of the head arriving, or its read fails, the response drain
|
|
49
|
+
included, and the connection closes)."""
|
|
50
|
+
|
|
51
|
+
keep_alive: Seconds
|
|
52
|
+
headers: Seconds
|
|
53
|
+
request: Seconds
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _reject(conn: Connection, status: int) -> Effect[None]:
|
|
57
|
+
head = (
|
|
58
|
+
f"HTTP/1.1 {status} {HTTPStatus(status).phrase}\r\n"
|
|
59
|
+
"connection: close\r\ncontent-length: 0\r\n\r\n"
|
|
60
|
+
).encode("ascii")
|
|
61
|
+
|
|
62
|
+
def ignore(_: SocketError) -> Effect[None]:
|
|
63
|
+
return void
|
|
64
|
+
|
|
65
|
+
return conn.write((head,)).catch_all(ignore).flat_map(lambda _: _close(conn))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _close(conn: Connection) -> Effect[None]:
|
|
69
|
+
"""End the response side, read on for the grace period, then close."""
|
|
70
|
+
|
|
71
|
+
def start() -> None:
|
|
72
|
+
conn.end_writing()
|
|
73
|
+
conn.discard_input()
|
|
74
|
+
|
|
75
|
+
waited = timeout_option(conn.closed(), CLOSE_GRACE_PERIOD)
|
|
76
|
+
return (
|
|
77
|
+
sync(start)
|
|
78
|
+
.flat_map(lambda _: waited)
|
|
79
|
+
.flat_map(lambda _: sync(lambda: conn.close(graceful=False)))
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _peer_address(conn: Connection) -> str | None:
|
|
84
|
+
name: Any = conn.peer_name()
|
|
85
|
+
if isinstance(name, tuple) and name:
|
|
86
|
+
return str(name[0]) # pyright: ignore[reportUnknownArgumentType]
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _request(
|
|
91
|
+
exchange: Exchange,
|
|
92
|
+
protocol: RequestProtocol,
|
|
93
|
+
remote_address: str | None,
|
|
94
|
+
websocket_max_message_size: int,
|
|
95
|
+
) -> SourceRequest:
|
|
96
|
+
head = exchange.head
|
|
97
|
+
url = head.target
|
|
98
|
+
body = exchange.body
|
|
99
|
+
request: SourceRequest | None = None
|
|
100
|
+
|
|
101
|
+
def upgrade_request(upgrade_headers: Headers) -> Effect[Any, Any]:
|
|
102
|
+
assert request is not None
|
|
103
|
+
return upgrade(exchange, request, upgrade_headers, websocket_max_message_size)
|
|
104
|
+
|
|
105
|
+
request = SourceRequest(
|
|
106
|
+
head.method,
|
|
107
|
+
url if url == "*" else absolute_url(protocol, head.authority or LOCALHOST, url),
|
|
108
|
+
head.headers,
|
|
109
|
+
None if body.complete else body.stream,
|
|
110
|
+
source=exchange,
|
|
111
|
+
remote_address=remote_address,
|
|
112
|
+
protocol=protocol,
|
|
113
|
+
upgrade=upgrade_request if head.upgrade is not None else None,
|
|
114
|
+
)
|
|
115
|
+
return request
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _run_request(exchange: Exchange, handled: Effect[None]) -> Effect[None]:
|
|
119
|
+
"""Run the request on a fiber interrupted as a client abort if the
|
|
120
|
+
client goes first: once it stops sending (Node's servers do not allow
|
|
121
|
+
half-open connections), or for an upgrade once the connection is
|
|
122
|
+
lost."""
|
|
123
|
+
conn = exchange.conn
|
|
124
|
+
gone = conn.closed() if exchange.head.upgrade is not None else conn.read_ended()
|
|
125
|
+
|
|
126
|
+
def body():
|
|
127
|
+
fiber: Fiber[None, Any] = yield from handled.fork()
|
|
128
|
+
|
|
129
|
+
def abort(_: None) -> Effect[None]:
|
|
130
|
+
if exchange.finished:
|
|
131
|
+
return void
|
|
132
|
+
exchange.close_after = True
|
|
133
|
+
exchange.aborted = True
|
|
134
|
+
return sync(lambda: fiber.interrupt_as_unsafe(None, CLIENT_ABORT_ANNOTATION))
|
|
135
|
+
|
|
136
|
+
watcher = yield from gone.flat_map(abort).fork()
|
|
137
|
+
yield from fiber.await_exit()
|
|
138
|
+
yield from watcher.interrupt()
|
|
139
|
+
|
|
140
|
+
return gen(body)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def serve_connection(
|
|
144
|
+
listener: Listener,
|
|
145
|
+
state: ConnectionState,
|
|
146
|
+
handle: Handle,
|
|
147
|
+
protocol: RequestProtocol,
|
|
148
|
+
timeouts: Timeouts,
|
|
149
|
+
*,
|
|
150
|
+
websocket_max_message_size: int,
|
|
151
|
+
) -> Effect[None]:
|
|
152
|
+
conn = state.conn
|
|
153
|
+
inbound = Inbound(conn)
|
|
154
|
+
remote_address = _peer_address(conn)
|
|
155
|
+
|
|
156
|
+
def head_failed(
|
|
157
|
+
error: SocketError | HeadTooLarge | BareLineFeed,
|
|
158
|
+
) -> Effect[bytes | int | None]:
|
|
159
|
+
if isinstance(error, HeadTooLarge):
|
|
160
|
+
return succeed(431)
|
|
161
|
+
return succeed(400 if isinstance(error, BareLineFeed) else None)
|
|
162
|
+
|
|
163
|
+
def within_headers_timeout(found: Option[bytes | int | None]) -> bytes | int | None:
|
|
164
|
+
if found is None:
|
|
165
|
+
return 408 if inbound.has_pending else None
|
|
166
|
+
return found.value
|
|
167
|
+
|
|
168
|
+
def next_head(first: bool) -> Effect[bytes | int | None]:
|
|
169
|
+
"""The next head, or the status to reject it with; ``None`` to
|
|
170
|
+
close quietly."""
|
|
171
|
+
head = timeout_option(
|
|
172
|
+
inbound.read_head(MAX_HEAD_SIZE).catch_all(head_failed), timeouts.headers
|
|
173
|
+
).map(within_headers_timeout)
|
|
174
|
+
if first:
|
|
175
|
+
return head
|
|
176
|
+
|
|
177
|
+
def started(_: SocketError) -> Effect[None]:
|
|
178
|
+
return void # the head read sees the failure
|
|
179
|
+
|
|
180
|
+
waited = timeout_option(inbound.wait_data().catch_all(started), timeouts.keep_alive)
|
|
181
|
+
return waited.flat_map(lambda found: succeed(None) if found is None else head)
|
|
182
|
+
|
|
183
|
+
def parsed(data: bytes | int | None) -> RequestHead | int | None:
|
|
184
|
+
if not isinstance(data, bytes):
|
|
185
|
+
return data
|
|
186
|
+
try:
|
|
187
|
+
return parse_head(data)
|
|
188
|
+
except HeadError as error:
|
|
189
|
+
return error.status
|
|
190
|
+
|
|
191
|
+
def after(exchange: Exchange) -> Effect[bool]:
|
|
192
|
+
"""Whether the connection serves another request."""
|
|
193
|
+
if exchange.upgraded is not None or not exchange.finished:
|
|
194
|
+
return succeed(False)
|
|
195
|
+
if exchange.close_after:
|
|
196
|
+
return _close(conn).as_(False)
|
|
197
|
+
return exchange.body.drain(MAX_DRAIN_BYTES).flat_map(
|
|
198
|
+
lambda drained: succeed(True) if drained else _close(conn).as_(False)
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
def loop():
|
|
202
|
+
first = True
|
|
203
|
+
# As Node's server, a connection busy when shutdown starts goes on
|
|
204
|
+
# serving keep-alive requests until it is idle for the keep-alive
|
|
205
|
+
# timeout (or the graceful shutdown timeout ends).
|
|
206
|
+
while True:
|
|
207
|
+
state.idle = True
|
|
208
|
+
inbound.deadline = None
|
|
209
|
+
data = yield from next_head(first)
|
|
210
|
+
inbound.deadline = time.monotonic() + timeouts.request
|
|
211
|
+
first = False
|
|
212
|
+
state.idle = False
|
|
213
|
+
head = parsed(data)
|
|
214
|
+
if head is None:
|
|
215
|
+
return
|
|
216
|
+
if isinstance(head, int):
|
|
217
|
+
yield from _reject(conn, head)
|
|
218
|
+
return
|
|
219
|
+
exchange = Exchange(conn, inbound, head, int(timeouts.keep_alive))
|
|
220
|
+
request = _request(exchange, protocol, remote_address, websocket_max_message_size)
|
|
221
|
+
yield from _run_request(exchange, handle(request, exchange))
|
|
222
|
+
if not (yield from after(exchange)):
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
return gen(loop).ensuring(sync(lambda: listener.done(state)))
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""The server's own timers (keep-alive, headers, close grace, shutdown)
|
|
2
|
+
run on the system clock, as Node's socket timers do: a ``TestClock`` the
|
|
3
|
+
program provides never moves them, so it must not stop them either."""
|
|
4
|
+
|
|
5
|
+
from pyfect._core import Effect
|
|
6
|
+
from pyfect.clock import Clock, LiveClock
|
|
7
|
+
from pyfect.duration import Seconds
|
|
8
|
+
from pyfect.effect import provide_service
|
|
9
|
+
from pyfect.option import Option
|
|
10
|
+
|
|
11
|
+
_LIVE = LiveClock()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def timeout_option[A, E, R](eff: Effect[A, E, R], seconds: Seconds) -> Effect[Option[A], E, R]:
|
|
15
|
+
"""``eff.timeout_option(seconds)`` timed by the system clock; ``eff``
|
|
16
|
+
is the server's own I/O, which reads no clock."""
|
|
17
|
+
return eff.timeout_option(seconds).pipe(provide_service(Clock, _LIVE))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ["timeout_option"]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Upgrading a connection to another protocol (RFC 9110 §7.8), as
|
|
2
|
+
Effect's ``NodeHttpServer.makeUpgradeHandler``: ``Upgrade: websocket`` is a
|
|
3
|
+
WebSocket (``_websocket``, which takes no custom response headers); for
|
|
4
|
+
any other protocol the handler's ``request.upgrade`` answers ``101
|
|
5
|
+
Switching Protocols`` once it takes the socket's reader, after the request
|
|
6
|
+
body has ended (its unread rest discarded, up to ``MaxBodySize``), and the
|
|
7
|
+
raw socket reads the bytes the client sent after the request first."""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
from typing import Never
|
|
13
|
+
|
|
14
|
+
from pyfect._core import Effect
|
|
15
|
+
from pyfect.effect import fail, succeed, suspend, sync, void
|
|
16
|
+
from pyfect.http.headers import Headers
|
|
17
|
+
from pyfect.http.incoming_message import MaxBodySize
|
|
18
|
+
from pyfect.http.live_server._exchange import (
|
|
19
|
+
MAX_DRAIN_BYTES,
|
|
20
|
+
Exchange,
|
|
21
|
+
InvalidResponse,
|
|
22
|
+
encode_fields,
|
|
23
|
+
)
|
|
24
|
+
from pyfect.http.live_server._websocket import web_socket
|
|
25
|
+
from pyfect.http.server_error import HttpServerError, InternalError, request_parse_error
|
|
26
|
+
from pyfect.http.server_request import ServerRequest
|
|
27
|
+
from pyfect.http.server_response import empty
|
|
28
|
+
from pyfect.scope import Scope
|
|
29
|
+
from pyfect.socket._errors import CloseEvent, SocketError, upgrade_error
|
|
30
|
+
from pyfect.socket._socket import Chunk, Reader, SocketService, Writer, encode_chunk
|
|
31
|
+
from pyfect.socket._socket import make as make_socket
|
|
32
|
+
|
|
33
|
+
_RESERVED = frozenset({"connection", "upgrade", "content-length", "transfer-encoding"})
|
|
34
|
+
_SWITCHED = empty(status=101)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _head(protocol: str, headers: Headers) -> bytes:
|
|
38
|
+
fields = [("Connection", "Upgrade"), ("Upgrade", protocol), *headers.items()]
|
|
39
|
+
return encode_fields("HTTP/1.1 101 Switching Protocols", fields)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _upgrade_error(
|
|
43
|
+
request: ServerRequest, description: str, cause: object = None
|
|
44
|
+
) -> HttpServerError:
|
|
45
|
+
"""Effect's ``upgradeError``: an ``InternalError``."""
|
|
46
|
+
return HttpServerError(InternalError(request, description, cause))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def upgrade(
|
|
50
|
+
exchange: Exchange, request: ServerRequest, headers: Headers, websocket_max_message_size: int
|
|
51
|
+
) -> Effect[SocketService, HttpServerError]:
|
|
52
|
+
"""The connection of an ``Upgrade`` request: a WebSocket or raw."""
|
|
53
|
+
protocol = exchange.head.upgrade
|
|
54
|
+
if protocol is None:
|
|
55
|
+
return fail(request_parse_error(request, "Not an upgradeable ServerRequest"))
|
|
56
|
+
if protocol.lower() == "websocket":
|
|
57
|
+
if headers:
|
|
58
|
+
message = "WebSocket upgrades do not support custom response headers"
|
|
59
|
+
return fail(_upgrade_error(request, message))
|
|
60
|
+
return succeed(web_socket(exchange, websocket_max_message_size))
|
|
61
|
+
reserved = sorted(_RESERVED.intersection(headers))
|
|
62
|
+
if reserved:
|
|
63
|
+
return fail(_upgrade_error(request, f"Reserved upgrade header: {reserved[0]}"))
|
|
64
|
+
try:
|
|
65
|
+
head = _head(protocol, headers)
|
|
66
|
+
except InvalidResponse as cause:
|
|
67
|
+
return fail(_upgrade_error(request, str(cause), cause))
|
|
68
|
+
conn = exchange.conn
|
|
69
|
+
|
|
70
|
+
def switch() -> Effect[None, SocketError]:
|
|
71
|
+
if exchange.upgraded is not None:
|
|
72
|
+
return void
|
|
73
|
+
if exchange.headers_sent:
|
|
74
|
+
return fail(upgrade_error(RuntimeError("the response was already sent")))
|
|
75
|
+
exchange.headers_sent = True
|
|
76
|
+
exchange.upgraded = _SWITCHED
|
|
77
|
+
exchange.close_after = True
|
|
78
|
+
return conn.write((head,))
|
|
79
|
+
|
|
80
|
+
def drained(ok: bool) -> Effect[None, SocketError]:
|
|
81
|
+
if not ok:
|
|
82
|
+
return fail(upgrade_error(RuntimeError("the request body is too large")))
|
|
83
|
+
return suspend(switch)
|
|
84
|
+
|
|
85
|
+
def opened(_: None) -> Reader:
|
|
86
|
+
leftover = exchange.take_pending()
|
|
87
|
+
first: list[bytes] = [leftover] if leftover else []
|
|
88
|
+
|
|
89
|
+
def pull() -> Effect[tuple[bytes, ...], SocketError]:
|
|
90
|
+
return succeed((first.pop(),)) if first else conn.pull()
|
|
91
|
+
|
|
92
|
+
return Reader(suspend(pull))
|
|
93
|
+
|
|
94
|
+
def reader(limit: int | None) -> Effect[Reader, SocketError]:
|
|
95
|
+
maximum = MAX_DRAIN_BYTES if limit is None else limit
|
|
96
|
+
return exchange.body.drain(maximum).flat_map(drained).map(opened)
|
|
97
|
+
|
|
98
|
+
def write(chunk: Chunk | CloseEvent) -> Effect[None, SocketError]:
|
|
99
|
+
if isinstance(chunk, CloseEvent):
|
|
100
|
+
return sync(lambda: conn.close(graceful=chunk.code <= 1000))
|
|
101
|
+
return conn.write((encode_chunk(chunk),))
|
|
102
|
+
|
|
103
|
+
def write_all(chunks: Sequence[Chunk]) -> Effect[None, SocketError]:
|
|
104
|
+
return conn.write(tuple(encode_chunk(chunk) for chunk in chunks))
|
|
105
|
+
|
|
106
|
+
opened_reader: Effect[Reader, SocketError, Scope] = MaxBodySize().flat_map(reader)
|
|
107
|
+
writer: Effect[Writer, Never, Scope] = succeed(Writer(write, write_all))
|
|
108
|
+
return succeed(make_socket(opened_reader, writer))
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""A WebSocket upgrade on the live server (Effect's ``NodeHttpServer``
|
|
2
|
+
handing ``Upgrade: websocket`` requests to ``ws``'s ``handleUpgrade``).
|
|
3
|
+
|
|
4
|
+
Acquiring the socket's reader checks the handshake as ``ws`` does (a bad
|
|
5
|
+
one is answered with ``ws``'s 400 or 405 and fails the reader as a socket
|
|
6
|
+
closed before the upgrade), sends the ``101`` with ``Sec-WebSocket-Accept``
|
|
7
|
+
(and the client's first subprotocol), then speaks RFC 6455: pulls give
|
|
8
|
+
the client's messages (text as ``str``), pings are answered, and the
|
|
9
|
+
client's close is answered and fails the pull with its code (1006 when the
|
|
10
|
+
connection ends without one). The reader's scope closes the WebSocket with
|
|
11
|
+
the handler's code (1000, 1001 when interrupted, 1011 on failure) unless a
|
|
12
|
+
close was already sent. Writes wait for the reader to open the socket; a
|
|
13
|
+
``CloseEvent`` sends a close frame, and writes after a close are dropped,
|
|
14
|
+
as ``ws`` drops them."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import threading
|
|
19
|
+
from collections import deque
|
|
20
|
+
from collections.abc import Sequence
|
|
21
|
+
from typing import Never
|
|
22
|
+
|
|
23
|
+
from pyfect._core import Effect
|
|
24
|
+
from pyfect._js import text_encode
|
|
25
|
+
from pyfect.effect import fail, succeed, suspend, sync, void
|
|
26
|
+
from pyfect.exit import Exit
|
|
27
|
+
from pyfect.http._field import is_token
|
|
28
|
+
from pyfect.http.live_server._exchange import Exchange, encode_fields
|
|
29
|
+
from pyfect.http.live_server._ws_frames import (
|
|
30
|
+
Close,
|
|
31
|
+
FrameParser,
|
|
32
|
+
Message,
|
|
33
|
+
Opcode,
|
|
34
|
+
Ping,
|
|
35
|
+
ProtocolError,
|
|
36
|
+
accept_key,
|
|
37
|
+
encode_close,
|
|
38
|
+
encode_frame,
|
|
39
|
+
encode_message,
|
|
40
|
+
is_valid_close_code,
|
|
41
|
+
is_valid_key,
|
|
42
|
+
)
|
|
43
|
+
from pyfect.http.server_response import empty
|
|
44
|
+
from pyfect.latch import Latch
|
|
45
|
+
from pyfect.scope import Scope, acquire_release
|
|
46
|
+
from pyfect.socket._errors import (
|
|
47
|
+
CloseEvent,
|
|
48
|
+
SocketError,
|
|
49
|
+
close_error,
|
|
50
|
+
open_error,
|
|
51
|
+
read_error,
|
|
52
|
+
write_error,
|
|
53
|
+
)
|
|
54
|
+
from pyfect.socket._socket import Chunk, Reader, SocketService, Writer, make
|
|
55
|
+
from pyfect.socket._web_socket import close_code
|
|
56
|
+
|
|
57
|
+
_SWITCHED = empty(status=101)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class _Refused(Exception):
|
|
61
|
+
def __init__(self, status: int, phrase: str, message: str, *extra: tuple[str, str]) -> None:
|
|
62
|
+
super().__init__(message)
|
|
63
|
+
self.status = status
|
|
64
|
+
self.phrase = phrase
|
|
65
|
+
self.extra = extra
|
|
66
|
+
|
|
67
|
+
def response(self) -> bytes:
|
|
68
|
+
body = text_encode(str(self))
|
|
69
|
+
fields = [
|
|
70
|
+
("Connection", "close"),
|
|
71
|
+
("Content-Type", "text/html"),
|
|
72
|
+
("Content-Length", str(len(body))),
|
|
73
|
+
*self.extra,
|
|
74
|
+
]
|
|
75
|
+
return encode_fields(f"HTTP/1.1 {self.status} {self.phrase}", fields) + body
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _subprotocols(value: str | None) -> list[str]:
|
|
79
|
+
if value is None:
|
|
80
|
+
return []
|
|
81
|
+
protocols = [part.strip(" \t") for part in value.split(",")]
|
|
82
|
+
if not all(map(is_token, protocols)) or any(
|
|
83
|
+
p in protocols[:i] for i, p in enumerate(protocols)
|
|
84
|
+
):
|
|
85
|
+
raise _Refused(400, "Bad Request", "Invalid Sec-WebSocket-Protocol header")
|
|
86
|
+
return protocols
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _switching_head(exchange: Exchange) -> bytes:
|
|
90
|
+
"""The ``101``, or ``_Refused`` for a handshake ``ws`` refuses."""
|
|
91
|
+
head = exchange.head
|
|
92
|
+
headers = head.headers
|
|
93
|
+
if head.method != "GET":
|
|
94
|
+
raise _Refused(405, "Method Not Allowed", "Invalid HTTP method")
|
|
95
|
+
key = headers.get("sec-websocket-key")
|
|
96
|
+
if key is None or not is_valid_key(key):
|
|
97
|
+
raise _Refused(400, "Bad Request", "Missing or invalid Sec-WebSocket-Key header")
|
|
98
|
+
if headers.get("sec-websocket-version") not in ("13", "8"):
|
|
99
|
+
raise _Refused(
|
|
100
|
+
400,
|
|
101
|
+
"Bad Request",
|
|
102
|
+
"Missing or invalid Sec-WebSocket-Version header",
|
|
103
|
+
("Sec-WebSocket-Version", "13, 8"),
|
|
104
|
+
)
|
|
105
|
+
protocols = _subprotocols(headers.get("sec-websocket-protocol"))
|
|
106
|
+
fields = [
|
|
107
|
+
("Upgrade", "websocket"),
|
|
108
|
+
("Connection", "Upgrade"),
|
|
109
|
+
("Sec-WebSocket-Accept", accept_key(key)),
|
|
110
|
+
]
|
|
111
|
+
if protocols:
|
|
112
|
+
fields.append(("Sec-WebSocket-Protocol", protocols[0]))
|
|
113
|
+
return encode_fields("HTTP/1.1 101 Switching Protocols", fields)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _closed_before_upgrade() -> SocketError:
|
|
117
|
+
return open_error(ConnectionError("The socket closed before the upgrade"))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class _Session:
|
|
121
|
+
"""One upgraded connection: what the reader parsed and has yet to hand
|
|
122
|
+
over or answer, and whether a close frame was sent."""
|
|
123
|
+
|
|
124
|
+
__slots__ = (
|
|
125
|
+
"_end_writing",
|
|
126
|
+
"_error",
|
|
127
|
+
"_leftover",
|
|
128
|
+
"_lock",
|
|
129
|
+
"_parser",
|
|
130
|
+
"_ready",
|
|
131
|
+
"_replies",
|
|
132
|
+
"close_sent",
|
|
133
|
+
"exchange",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def __init__(self, exchange: Exchange, max_message_size: int) -> None:
|
|
137
|
+
self.exchange = exchange
|
|
138
|
+
self._parser = FrameParser(max_message_size)
|
|
139
|
+
self._leftover = exchange.take_pending()
|
|
140
|
+
self._lock = threading.Lock()
|
|
141
|
+
self._ready: deque[Chunk] = deque()
|
|
142
|
+
self._replies: list[bytes] = []
|
|
143
|
+
self._end_writing = False
|
|
144
|
+
self._error: SocketError | None = None
|
|
145
|
+
self.close_sent = False
|
|
146
|
+
|
|
147
|
+
def claim_close(self) -> bool:
|
|
148
|
+
"""Whether this call is the one that sends the close frame."""
|
|
149
|
+
with self._lock:
|
|
150
|
+
if self.close_sent:
|
|
151
|
+
return False
|
|
152
|
+
self.close_sent = True
|
|
153
|
+
return True
|
|
154
|
+
|
|
155
|
+
# --- reading ------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
def _feed(self, chunks: Sequence[bytes]) -> None:
|
|
158
|
+
for chunk in chunks:
|
|
159
|
+
try:
|
|
160
|
+
events = self._parser.feed(chunk)
|
|
161
|
+
except ProtocolError as error:
|
|
162
|
+
self._error = read_error(error)
|
|
163
|
+
if self.claim_close():
|
|
164
|
+
# ``ws`` closes with the code alone (``receiverOnError``).
|
|
165
|
+
self._replies.append(encode_close(error.code))
|
|
166
|
+
self._end_writing = True
|
|
167
|
+
return
|
|
168
|
+
for event in events:
|
|
169
|
+
match event:
|
|
170
|
+
case Message(data):
|
|
171
|
+
self._ready.append(data)
|
|
172
|
+
case Ping(payload):
|
|
173
|
+
if not self.close_sent:
|
|
174
|
+
self._replies.append(encode_frame(Opcode.PONG, payload))
|
|
175
|
+
case Close(code, reason):
|
|
176
|
+
self._error = close_error(code, reason)
|
|
177
|
+
if self.claim_close():
|
|
178
|
+
self._replies.append(
|
|
179
|
+
encode_close(None if code == 1005 else code, reason)
|
|
180
|
+
)
|
|
181
|
+
self._end_writing = True
|
|
182
|
+
|
|
183
|
+
def _lost(self, _: SocketError) -> Effect[None]:
|
|
184
|
+
if self._error is None:
|
|
185
|
+
self._error = close_error(1006)
|
|
186
|
+
return void
|
|
187
|
+
|
|
188
|
+
def pull(self) -> Effect[tuple[Chunk, ...], SocketError]:
|
|
189
|
+
conn = self.exchange.conn
|
|
190
|
+
|
|
191
|
+
def step() -> Effect[tuple[Chunk, ...], SocketError]:
|
|
192
|
+
if self._replies:
|
|
193
|
+
replies = tuple(self._replies)
|
|
194
|
+
self._replies.clear()
|
|
195
|
+
return conn.write(replies).catch_all(self._lost).flat_map(lambda _: suspend(step))
|
|
196
|
+
if self._end_writing:
|
|
197
|
+
self._end_writing = False
|
|
198
|
+
conn.end_writing()
|
|
199
|
+
if self._ready:
|
|
200
|
+
ready = tuple(self._ready)
|
|
201
|
+
self._ready.clear()
|
|
202
|
+
return succeed(ready)
|
|
203
|
+
if self._error is not None:
|
|
204
|
+
return fail(self._error)
|
|
205
|
+
if self._leftover:
|
|
206
|
+
leftover, self._leftover = self._leftover, b""
|
|
207
|
+
self._feed((leftover,))
|
|
208
|
+
return suspend(step)
|
|
209
|
+
return (
|
|
210
|
+
conn.pull_into(self._feed).catch_all(self._lost).flat_map(lambda _: suspend(step))
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return suspend(step)
|
|
214
|
+
|
|
215
|
+
# --- writing --------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
def _send(self, frames: tuple[bytes, ...]) -> Effect[None, SocketError]:
|
|
218
|
+
# As ``ws``: a send on a closed connection is dropped, not raised;
|
|
219
|
+
# the reader sees the connection end.
|
|
220
|
+
return self.exchange.conn.write(frames).catch_all(lambda _: void)
|
|
221
|
+
|
|
222
|
+
def write(self, chunk: Chunk | CloseEvent) -> Effect[None, SocketError]:
|
|
223
|
+
if not isinstance(chunk, CloseEvent):
|
|
224
|
+
return void if self.close_sent else self._send((encode_message(chunk),))
|
|
225
|
+
reason = chunk.reason or ""
|
|
226
|
+
if not is_valid_close_code(chunk.code):
|
|
227
|
+
return fail(write_error(TypeError("First argument must be a valid error code number")))
|
|
228
|
+
if len(text_encode(reason)) > 123:
|
|
229
|
+
return fail(write_error(ValueError("The message must not be greater than 123 bytes")))
|
|
230
|
+
if not self.claim_close():
|
|
231
|
+
return void
|
|
232
|
+
return self._send((encode_close(chunk.code, reason),))
|
|
233
|
+
|
|
234
|
+
def write_all(self, chunks: Sequence[Chunk]) -> Effect[None, SocketError]:
|
|
235
|
+
if self.close_sent:
|
|
236
|
+
return void
|
|
237
|
+
return self._send(tuple(map(encode_message, chunks)))
|
|
238
|
+
|
|
239
|
+
def close(self, exit: Exit[object, object]) -> Effect[None]:
|
|
240
|
+
if not self.claim_close():
|
|
241
|
+
return void
|
|
242
|
+
return self._send((encode_close(close_code(exit)),)).catch_all(lambda _: void)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def web_socket(exchange: Exchange, max_message_size: int) -> SocketService:
|
|
246
|
+
"""The socket of a WebSocket upgrade request."""
|
|
247
|
+
opened = Latch(False)
|
|
248
|
+
current: list[_Session] = []
|
|
249
|
+
|
|
250
|
+
def handshake() -> Effect[_Session, SocketError]:
|
|
251
|
+
conn = exchange.conn
|
|
252
|
+
if exchange.upgraded is not None or exchange.headers_sent:
|
|
253
|
+
return fail(_closed_before_upgrade())
|
|
254
|
+
exchange.headers_sent = True
|
|
255
|
+
exchange.close_after = True
|
|
256
|
+
try:
|
|
257
|
+
head = _switching_head(exchange)
|
|
258
|
+
except _Refused as refused:
|
|
259
|
+
written = conn.write((refused.response(),)).catch_all(lambda _: void)
|
|
260
|
+
return written.flat_map(lambda _: sync(conn.end_writing)).flat_map(
|
|
261
|
+
lambda _: fail(_closed_before_upgrade())
|
|
262
|
+
)
|
|
263
|
+
exchange.upgraded = _SWITCHED
|
|
264
|
+
session = _Session(exchange, max_message_size)
|
|
265
|
+
return conn.write((head,)).map_error(lambda _: _closed_before_upgrade()).as_(session)
|
|
266
|
+
|
|
267
|
+
def release(session: _Session, exit: Exit[object, object]) -> Effect[None]:
|
|
268
|
+
opened.close_unsafe()
|
|
269
|
+
current.clear()
|
|
270
|
+
return session.close(exit)
|
|
271
|
+
|
|
272
|
+
def open_reader(session: _Session) -> Reader:
|
|
273
|
+
current.append(session)
|
|
274
|
+
opened.open_unsafe()
|
|
275
|
+
return Reader(session.pull())
|
|
276
|
+
|
|
277
|
+
reader = acquire_release(suspend(handshake), release).map(open_reader)
|
|
278
|
+
|
|
279
|
+
def write(chunk: Chunk | CloseEvent) -> Effect[None, SocketError]:
|
|
280
|
+
return opened.when_open(suspend(lambda: current[0].write(chunk)))
|
|
281
|
+
|
|
282
|
+
def write_all(chunks: Sequence[Chunk]) -> Effect[None, SocketError]:
|
|
283
|
+
return opened.when_open(suspend(lambda: current[0].write_all(chunks)))
|
|
284
|
+
|
|
285
|
+
writer: Effect[Writer, Never, Scope] = succeed(Writer(write, write_all))
|
|
286
|
+
return make(reader, writer)
|