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,729 @@
|
|
|
1
|
+
"""The request a server handles (Effect's ``HttpServerRequest``).
|
|
2
|
+
|
|
3
|
+
A ``ServerRequest`` is its method, URL (``url``, as the router sees it,
|
|
4
|
+
and ``original_url``, as received), headers, cookies, addresses, and its
|
|
5
|
+
body, read as ``bytes_``, ``text``, ``json``, ``url_params_body``,
|
|
6
|
+
``stream``, or as multipart parts (``multipart_stream``) and a persisted
|
|
7
|
+
form (``multipart``). A body is read once: every read after the first
|
|
8
|
+
gives the same bytes, and ``MaxBodySize`` bounds it (``BodyTooLarge``,
|
|
9
|
+
413). Handlers get the current request from the ``HttpServerRequest``
|
|
10
|
+
service::
|
|
11
|
+
|
|
12
|
+
request = yield from HttpServerRequest()
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# pyright: reportPrivateUsage=false
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from abc import ABC, abstractmethod
|
|
20
|
+
from collections.abc import Callable, Mapping
|
|
21
|
+
from functools import cached_property
|
|
22
|
+
from typing import TYPE_CHECKING, Any, Literal, Unpack, cast, final
|
|
23
|
+
from urllib.parse import SplitResult
|
|
24
|
+
|
|
25
|
+
from pyfect._cached import memoize
|
|
26
|
+
from pyfect._core import Effect, _WithFiber
|
|
27
|
+
from pyfect._js import text_encode
|
|
28
|
+
from pyfect._json_text import PARSE_ERRORS, Json, parse
|
|
29
|
+
from pyfect.context import Tag
|
|
30
|
+
from pyfect.effect import fail, succeed, suspend
|
|
31
|
+
from pyfect.file_system import FileSystem
|
|
32
|
+
from pyfect.http import _form_data, incoming_message
|
|
33
|
+
from pyfect.http import body as _body
|
|
34
|
+
from pyfect.http import url as _url
|
|
35
|
+
from pyfect.http._authority import ROOT, absolute_url, parse_authority, parse_origin_form
|
|
36
|
+
from pyfect.http.cookies import parse_header as parse_cookie_header
|
|
37
|
+
from pyfect.http.headers import Headers
|
|
38
|
+
from pyfect.http.headers import Input as HeadersInput
|
|
39
|
+
from pyfect.http.incoming_message import MaxBodySize, Reviver
|
|
40
|
+
from pyfect.http.method import HttpMethod, has_body
|
|
41
|
+
from pyfect.http.request import HttpClientRequest
|
|
42
|
+
from pyfect.http.server_error import HttpServerError, RequestParseError, request_parse_error
|
|
43
|
+
from pyfect.http.server_response import parse_content_length
|
|
44
|
+
from pyfect.http.url_params import EMPTY as EMPTY_URL_PARAMS
|
|
45
|
+
from pyfect.http.url_params import UrlParams
|
|
46
|
+
from pyfect.http.url_params import parse as parse_url_params
|
|
47
|
+
from pyfect.http.url_params import to_string as url_params_to_string
|
|
48
|
+
from pyfect.path import Path
|
|
49
|
+
from pyfect.result import Err, Ok, Result
|
|
50
|
+
from pyfect.schema import Codec, SchemaError, decode_unknown_effect
|
|
51
|
+
from pyfect.schema._options import ParseOptions
|
|
52
|
+
from pyfect.scope import Scope
|
|
53
|
+
from pyfect.socket import SocketService
|
|
54
|
+
from pyfect.stream import Stream, from_effect
|
|
55
|
+
from pyfect.stream import empty as empty_stream
|
|
56
|
+
from pyfect.stream import fail as fail_stream
|
|
57
|
+
from pyfect.stream import succeed as succeed_stream
|
|
58
|
+
from pyfect.stream._base import require_stream
|
|
59
|
+
|
|
60
|
+
if TYPE_CHECKING:
|
|
61
|
+
from pyfect.http.multipart import Part, Persisted
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
type RequestProtocol = Literal["http", "https"]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class _Unset:
|
|
68
|
+
__slots__ = ()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
_UNSET = _Unset()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ServerRequest(ABC):
|
|
75
|
+
"""A request the server is handling."""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
url: str | None = None,
|
|
80
|
+
headers: Headers | None = None,
|
|
81
|
+
remote_address: str | None = None,
|
|
82
|
+
peer_address: str | None = None,
|
|
83
|
+
protocol: RequestProtocol | None = None,
|
|
84
|
+
) -> None:
|
|
85
|
+
self._url: str | None = url
|
|
86
|
+
self._headers: Headers | None = headers
|
|
87
|
+
self._remote_address: str | None = remote_address
|
|
88
|
+
self._peer_address: str | None = peer_address
|
|
89
|
+
self._protocol: RequestProtocol | None = protocol
|
|
90
|
+
|
|
91
|
+
# --- what a source gives ---------------------------------------------------
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
@abstractmethod
|
|
95
|
+
def source(self) -> object:
|
|
96
|
+
"""What the request was made from (an ``HttpClientRequest``, the
|
|
97
|
+
live server's connection state, an ASGI scope)."""
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
@abstractmethod
|
|
101
|
+
def original_url(self) -> str:
|
|
102
|
+
"""The URL as received."""
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
@abstractmethod
|
|
106
|
+
def method(self) -> HttpMethod: ...
|
|
107
|
+
|
|
108
|
+
@abstractmethod
|
|
109
|
+
def _source_headers(self) -> Headers: ...
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
@abstractmethod
|
|
113
|
+
def stream(self) -> Stream[bytes, HttpServerError]:
|
|
114
|
+
"""The body as it arrives."""
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
@abstractmethod
|
|
118
|
+
def bytes_(self) -> Effect[bytes, HttpServerError]:
|
|
119
|
+
"""The whole body, read once (Effect's ``arrayBuffer``)."""
|
|
120
|
+
|
|
121
|
+
@abstractmethod
|
|
122
|
+
def _copy(self) -> ServerRequest:
|
|
123
|
+
"""A request on the same source and body, to be modified."""
|
|
124
|
+
|
|
125
|
+
def upgrade_with(
|
|
126
|
+
self, *, headers: HeadersInput | None = None
|
|
127
|
+
) -> Effect[SocketService, HttpServerError]:
|
|
128
|
+
"""Upgrade the connection to a WebSocket, answering with ``headers``
|
|
129
|
+
too; a request that cannot be upgraded fails."""
|
|
130
|
+
del headers
|
|
131
|
+
return fail(request_parse_error(self, "Not an upgradeable ServerRequest"))
|
|
132
|
+
|
|
133
|
+
# --- derived ---------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def url(self) -> str:
|
|
137
|
+
"""The path and query (the router may have removed a prefix)."""
|
|
138
|
+
return remove_host(self.original_url) if self._url is None else self._url
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def headers(self) -> Headers:
|
|
142
|
+
if self._headers is None:
|
|
143
|
+
self._headers = self._source_headers()
|
|
144
|
+
return self._headers
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def remote_address(self) -> str | None:
|
|
148
|
+
"""The client's address (behind proxies, as they report it)."""
|
|
149
|
+
return self._remote_address
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def peer_address(self) -> str | None:
|
|
153
|
+
"""The address of the connection's other end."""
|
|
154
|
+
return self._remote_address if self._peer_address is None else self._peer_address
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def protocol(self) -> RequestProtocol | None:
|
|
158
|
+
return self._protocol
|
|
159
|
+
|
|
160
|
+
@cached_property
|
|
161
|
+
def cookies(self) -> Mapping[str, str]:
|
|
162
|
+
return parse_cookie_header(self.headers.get("cookie", ""))
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def upgrade(self) -> Effect[SocketService, HttpServerError]:
|
|
166
|
+
return self.upgrade_with()
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def text(self) -> Effect[str, HttpServerError]:
|
|
170
|
+
"""The body as UTF-8 (invalid bytes become U+FFFD)."""
|
|
171
|
+
return self.bytes_.map(lambda data: data.decode("utf-8-sig", "replace"))
|
|
172
|
+
|
|
173
|
+
def _parse_json(self, text: str) -> Effect[Json, HttpServerError]:
|
|
174
|
+
try:
|
|
175
|
+
return succeed(parse(text))
|
|
176
|
+
except PARSE_ERRORS as cause:
|
|
177
|
+
return fail(request_parse_error(self, None, cause))
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def json(self) -> Effect[Json, HttpServerError]:
|
|
181
|
+
"""The body parsed as JSON."""
|
|
182
|
+
return self.text.flat_map(self._parse_json)
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def url_params_body(self) -> Effect[UrlParams, HttpServerError]:
|
|
186
|
+
return self.text.map(parse_url_params)
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def multipart_stream(self) -> Stream[Part, Any]:
|
|
190
|
+
"""The body's multipart parts, as they arrive."""
|
|
191
|
+
from pyfect.http.multipart import MultipartError, make_channel
|
|
192
|
+
|
|
193
|
+
chunks = self.stream.map_error(
|
|
194
|
+
lambda cause: MultipartError.from_reason("InternalError", cause)
|
|
195
|
+
)
|
|
196
|
+
return chunks.pipe_through_channel(make_channel(self.headers))
|
|
197
|
+
|
|
198
|
+
@cached_property
|
|
199
|
+
def multipart(self) -> Effect[Persisted, Any, Scope | FileSystem | Path]:
|
|
200
|
+
"""The body's multipart form, its files written to a scoped
|
|
201
|
+
temporary directory (once)."""
|
|
202
|
+
from pyfect.http.multipart import to_persisted
|
|
203
|
+
|
|
204
|
+
return memoize(to_persisted(self.multipart_stream))
|
|
205
|
+
|
|
206
|
+
def modify(
|
|
207
|
+
self,
|
|
208
|
+
*,
|
|
209
|
+
url: str | None = None,
|
|
210
|
+
headers: Headers | None = None,
|
|
211
|
+
remote_address: str | _Unset | None = _UNSET,
|
|
212
|
+
peer_address: str | None = None,
|
|
213
|
+
protocol: RequestProtocol | None = None,
|
|
214
|
+
) -> ServerRequest:
|
|
215
|
+
"""The request with these parts replaced (``remote_address=None``
|
|
216
|
+
removes it); its body is this request's."""
|
|
217
|
+
copy = self._copy()
|
|
218
|
+
copy._url = self.url if url is None else url
|
|
219
|
+
copy._headers = self._headers if headers is None else headers
|
|
220
|
+
copy._remote_address = (
|
|
221
|
+
self._remote_address if isinstance(remote_address, _Unset) else remote_address
|
|
222
|
+
)
|
|
223
|
+
copy._peer_address = self._peer_address if peer_address is None else peer_address
|
|
224
|
+
copy._protocol = self._protocol if protocol is None else protocol
|
|
225
|
+
return copy
|
|
226
|
+
|
|
227
|
+
def __repr__(self) -> str:
|
|
228
|
+
return f"ServerRequest({self.method} {self.url})"
|
|
229
|
+
|
|
230
|
+
def to_json(self) -> dict[str, object]:
|
|
231
|
+
from pyfect.http.url_params import redact_url
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
"_id": "HttpServerRequest",
|
|
235
|
+
"method": self.method,
|
|
236
|
+
"url": redact_url(self.original_url),
|
|
237
|
+
"headers": self.headers.to_json(),
|
|
238
|
+
"remoteAddress": self.remote_address,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class HttpServerRequest(Tag[ServerRequest], key="effect/http/HttpServerRequest"):
|
|
243
|
+
"""The request being handled."""
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class ParsedSearchParams(Tag[Mapping[str, str | list[str]]], key="effect/http/ParsedSearchParams"):
|
|
247
|
+
"""The request's query parameters: each name's value, or values when
|
|
248
|
+
repeated."""
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def remove_host(url: str) -> str:
|
|
252
|
+
"""The path and query of a URL that may be absolute."""
|
|
253
|
+
if url.startswith("/"):
|
|
254
|
+
return url
|
|
255
|
+
start = url.find("//")
|
|
256
|
+
index = url.find("/", start + 2)
|
|
257
|
+
return "/" if index == -1 else url[index:]
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def search_params_from_url(url: SplitResult) -> dict[str, str | list[str]]:
|
|
261
|
+
"""The query of ``url``: each name's value, or values when repeated."""
|
|
262
|
+
return parse_url_params(url.query).to_record()
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def to_url(request: ServerRequest) -> _url.Url | None:
|
|
266
|
+
"""The request's full URL, from its ``host`` header (``localhost``
|
|
267
|
+
without one) and protocol; ``None`` when that is no valid URL, or the
|
|
268
|
+
host is no authority (so it cannot change the path or query)."""
|
|
269
|
+
authority = parse_authority(request.headers.get("host") or "localhost")
|
|
270
|
+
if authority is None:
|
|
271
|
+
return None
|
|
272
|
+
protocol = request.protocol or (
|
|
273
|
+
"https" if request.original_url[:6].lower() == "https:" else "http"
|
|
274
|
+
)
|
|
275
|
+
url = request.url
|
|
276
|
+
origin = parse_origin_form(url)
|
|
277
|
+
try:
|
|
278
|
+
if origin is not None:
|
|
279
|
+
return _url.parse(absolute_url(protocol, authority, origin))
|
|
280
|
+
return _url.parse(url, absolute_url(protocol, authority, ROOT))
|
|
281
|
+
except ValueError:
|
|
282
|
+
return None
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def to_url_result(request: ServerRequest) -> Result[_url.Url, RequestParseError]:
|
|
286
|
+
url = to_url(request)
|
|
287
|
+
if url is None:
|
|
288
|
+
return Err(RequestParseError(request, "Invalid URL"))
|
|
289
|
+
return Ok(url)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# --- reading a body with a limit ----------------------------------------------
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class BodyTooLargeError(Exception):
|
|
296
|
+
def __init__(self, max_bytes: int) -> None:
|
|
297
|
+
super().__init__(f"Request body exceeds {max_bytes} bytes")
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def read_limited[E](
|
|
301
|
+
chunks: Stream[bytes, E], content_length: int | None, max_bytes: int | None
|
|
302
|
+
) -> Effect[bytes, E | BodyTooLargeError | ValueError]:
|
|
303
|
+
"""The bytes of ``chunks``; past ``max_bytes`` (or the declared
|
|
304
|
+
``content_length``) the read stops and fails."""
|
|
305
|
+
require_stream(chunks, "chunks")
|
|
306
|
+
if max_bytes is not None and content_length is not None and content_length > max_bytes:
|
|
307
|
+
return fail(BodyTooLargeError(max_bytes))
|
|
308
|
+
limit = content_length if content_length is not None else max_bytes
|
|
309
|
+
out = bytearray()
|
|
310
|
+
|
|
311
|
+
def add(chunk: bytes) -> Effect[None, BodyTooLargeError | ValueError]:
|
|
312
|
+
out.extend(chunk)
|
|
313
|
+
if limit is not None and len(out) > limit:
|
|
314
|
+
if max_bytes is not None and len(out) > max_bytes:
|
|
315
|
+
return fail(BodyTooLargeError(max_bytes))
|
|
316
|
+
return fail(
|
|
317
|
+
ValueError(f"Request body is longer than its content-length of {content_length}")
|
|
318
|
+
)
|
|
319
|
+
return succeed(None)
|
|
320
|
+
|
|
321
|
+
return chunks.run_for_each(add).map(lambda _: bytes(out))
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class _SharedBody:
|
|
325
|
+
"""A body read once and shared by a request and its modified copies."""
|
|
326
|
+
|
|
327
|
+
__slots__ = ("_bytes", "_open", "_open_read", "_read")
|
|
328
|
+
|
|
329
|
+
def __init__(
|
|
330
|
+
self,
|
|
331
|
+
open: Callable[[], Stream[bytes, object]] | None,
|
|
332
|
+
open_read: Callable[[], Stream[bytes, object]] | None = None,
|
|
333
|
+
) -> None:
|
|
334
|
+
self._open = open
|
|
335
|
+
self._open_read = open_read or open
|
|
336
|
+
self._read = False
|
|
337
|
+
self._bytes: Effect[bytes, HttpServerError] | None = None
|
|
338
|
+
|
|
339
|
+
def stream(self, request: ServerRequest) -> Stream[bytes, HttpServerError]:
|
|
340
|
+
if self._bytes is not None:
|
|
341
|
+
return _stream_of(self._bytes)
|
|
342
|
+
if self._open is None:
|
|
343
|
+
return fail_stream(
|
|
344
|
+
request_parse_error(request, "can not create stream from empty body")
|
|
345
|
+
)
|
|
346
|
+
opened = self._open
|
|
347
|
+
return opened().map_error(lambda cause: request_parse_error(request, None, cause))
|
|
348
|
+
|
|
349
|
+
def bytes_(self, request: ServerRequest) -> Effect[bytes, HttpServerError]:
|
|
350
|
+
if self._bytes is None:
|
|
351
|
+
self._bytes = memoize(
|
|
352
|
+
_WithFiber(lambda fiber: self._read_all(request, fiber.get_ref(MaxBodySize)))
|
|
353
|
+
)
|
|
354
|
+
return self._bytes
|
|
355
|
+
|
|
356
|
+
def _read_all(
|
|
357
|
+
self, request: ServerRequest, max_bytes: int | None
|
|
358
|
+
) -> Effect[bytes, HttpServerError]:
|
|
359
|
+
if self._open_read is None:
|
|
360
|
+
return succeed(b"")
|
|
361
|
+
content_length = parse_content_length(request.headers.get("content-length"))
|
|
362
|
+
|
|
363
|
+
def on_error(cause: object) -> HttpServerError:
|
|
364
|
+
if isinstance(cause, BodyTooLargeError):
|
|
365
|
+
return request_parse_error(request, None, cause, "BodyTooLarge")
|
|
366
|
+
return request_parse_error(request, None, cause)
|
|
367
|
+
|
|
368
|
+
return read_limited(self._open_read(), content_length, max_bytes).map_error(on_error)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _stream_of(data: Effect[bytes, HttpServerError]) -> Stream[bytes, HttpServerError]:
|
|
372
|
+
"""The bytes of a body already read, as a stream (none when empty)."""
|
|
373
|
+
return from_effect(data).filter(lambda chunk: len(chunk) > 0)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@final
|
|
377
|
+
class SourceRequest(ServerRequest):
|
|
378
|
+
"""A request a server received: its method, URL as sent, headers, and a
|
|
379
|
+
body that can be opened once (``None``: no body)."""
|
|
380
|
+
|
|
381
|
+
def __init__(
|
|
382
|
+
self,
|
|
383
|
+
method: str,
|
|
384
|
+
url: str,
|
|
385
|
+
headers: Headers,
|
|
386
|
+
body: Callable[[], Stream[bytes, object]] | None,
|
|
387
|
+
*,
|
|
388
|
+
source: object = None,
|
|
389
|
+
remote_address: str | None = None,
|
|
390
|
+
protocol: RequestProtocol | None = None,
|
|
391
|
+
upgrade: Callable[[Headers], Effect[SocketService, object]] | None = None,
|
|
392
|
+
read_body: Callable[[], Stream[bytes, object]] | None = None,
|
|
393
|
+
) -> None:
|
|
394
|
+
"""``read_body`` opens the body for reading it whole, when that
|
|
395
|
+
differs from streaming it."""
|
|
396
|
+
super().__init__(None, headers, remote_address, None, protocol)
|
|
397
|
+
self._method = method.upper()
|
|
398
|
+
self._original_url = url
|
|
399
|
+
self._body = _SharedBody(body, read_body)
|
|
400
|
+
self._source = source
|
|
401
|
+
self._upgrade = upgrade
|
|
402
|
+
|
|
403
|
+
@property
|
|
404
|
+
def source(self) -> object:
|
|
405
|
+
return self._source
|
|
406
|
+
|
|
407
|
+
@property
|
|
408
|
+
def original_url(self) -> str:
|
|
409
|
+
return self._original_url
|
|
410
|
+
|
|
411
|
+
@property
|
|
412
|
+
def method(self) -> HttpMethod:
|
|
413
|
+
return cast("HttpMethod", self._method)
|
|
414
|
+
|
|
415
|
+
def _source_headers(self) -> Headers:
|
|
416
|
+
raise AssertionError("a SourceRequest always has headers")
|
|
417
|
+
|
|
418
|
+
@property
|
|
419
|
+
def stream(self) -> Stream[bytes, HttpServerError]:
|
|
420
|
+
return self._body.stream(self)
|
|
421
|
+
|
|
422
|
+
@property
|
|
423
|
+
def bytes_(self) -> Effect[bytes, HttpServerError]:
|
|
424
|
+
return self._body.bytes_(self)
|
|
425
|
+
|
|
426
|
+
def upgrade_with(
|
|
427
|
+
self, *, headers: HeadersInput | None = None
|
|
428
|
+
) -> Effect[SocketService, HttpServerError]:
|
|
429
|
+
if self._upgrade is None:
|
|
430
|
+
return super().upgrade_with(headers=headers)
|
|
431
|
+
from pyfect.http.headers import from_input
|
|
432
|
+
from pyfect.http.origin import require_allowed_origin
|
|
433
|
+
|
|
434
|
+
upgrade = self._upgrade
|
|
435
|
+
upgraded = upgrade(from_input(headers)).map_error(
|
|
436
|
+
lambda cause: (
|
|
437
|
+
cause
|
|
438
|
+
if isinstance(cause, HttpServerError)
|
|
439
|
+
else request_parse_error(self, None, cause)
|
|
440
|
+
)
|
|
441
|
+
)
|
|
442
|
+
return require_allowed_origin(self).and_then(upgraded)
|
|
443
|
+
|
|
444
|
+
def _copy(self) -> ServerRequest:
|
|
445
|
+
copy = SourceRequest.__new__(SourceRequest)
|
|
446
|
+
vars(copy).update(
|
|
447
|
+
{k: v for k, v in vars(self).items() if k not in ("cookies", "multipart")}
|
|
448
|
+
)
|
|
449
|
+
return copy
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@final
|
|
453
|
+
class ClientRequest(ServerRequest):
|
|
454
|
+
"""A server request made from an ``HttpClientRequest`` (Effect's
|
|
455
|
+
``fromClientRequest``), as test clients send."""
|
|
456
|
+
|
|
457
|
+
def __init__(self, source: HttpClientRequest, original_url: str) -> None:
|
|
458
|
+
super().__init__()
|
|
459
|
+
self._source = source
|
|
460
|
+
self._original_url = original_url
|
|
461
|
+
self._bytes: Effect[bytes, HttpServerError] | None = None
|
|
462
|
+
|
|
463
|
+
@property
|
|
464
|
+
def source(self) -> HttpClientRequest:
|
|
465
|
+
return self._source
|
|
466
|
+
|
|
467
|
+
@property
|
|
468
|
+
def original_url(self) -> str:
|
|
469
|
+
return self._original_url
|
|
470
|
+
|
|
471
|
+
@property
|
|
472
|
+
def method(self) -> HttpMethod:
|
|
473
|
+
return self._source.method
|
|
474
|
+
|
|
475
|
+
def _source_headers(self) -> Headers:
|
|
476
|
+
return self._source.headers
|
|
477
|
+
|
|
478
|
+
@property
|
|
479
|
+
def stream(self) -> Stream[bytes, HttpServerError]:
|
|
480
|
+
body = self._source.body
|
|
481
|
+
match body:
|
|
482
|
+
case _body.Empty():
|
|
483
|
+
return empty_stream
|
|
484
|
+
case _body.Bytes():
|
|
485
|
+
return succeed_stream(body.body)
|
|
486
|
+
case _body.Stream():
|
|
487
|
+
return body.stream.map_error(lambda cause: request_parse_error(self, None, cause))
|
|
488
|
+
case _body.FormData():
|
|
489
|
+
return succeed_stream(_form_data.encode(body)[0])
|
|
490
|
+
case _body.Raw():
|
|
491
|
+
data = _raw_bytes(body.body)
|
|
492
|
+
if data is None:
|
|
493
|
+
return fail_stream(request_parse_error(self, "Unsupported body type"))
|
|
494
|
+
return succeed_stream(data)
|
|
495
|
+
|
|
496
|
+
@property
|
|
497
|
+
def bytes_(self) -> Effect[bytes, HttpServerError]:
|
|
498
|
+
if self._bytes is None:
|
|
499
|
+
self._bytes = memoize(suspend(self._read))
|
|
500
|
+
return self._bytes
|
|
501
|
+
|
|
502
|
+
def _read(self) -> Effect[bytes, HttpServerError]:
|
|
503
|
+
body = self._source.body
|
|
504
|
+
if isinstance(body, _body.Raw):
|
|
505
|
+
data = _raw_bytes(body.body)
|
|
506
|
+
if data is None:
|
|
507
|
+
return fail(request_parse_error(self, "Unsupported body type"))
|
|
508
|
+
return succeed(data)
|
|
509
|
+
return self.stream.run_collect().map(b"".join)
|
|
510
|
+
|
|
511
|
+
@property
|
|
512
|
+
def json(self) -> Effect[Json, HttpServerError]:
|
|
513
|
+
return self.text.flat_map(
|
|
514
|
+
lambda text: succeed(None) if text == "" else self._parse_json(text)
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
@property
|
|
518
|
+
def multipart_stream(self) -> Stream[Part, Any]:
|
|
519
|
+
form = form_data_body(self)
|
|
520
|
+
if form is None:
|
|
521
|
+
return super().multipart_stream
|
|
522
|
+
from pyfect.http.multipart import Field, File
|
|
523
|
+
from pyfect.stream import from_iterable
|
|
524
|
+
|
|
525
|
+
parts: list[Part] = [
|
|
526
|
+
Field(key, "text/plain", value)
|
|
527
|
+
if isinstance(value, str)
|
|
528
|
+
else File(key, value.name, value.content_type, succeed_stream(value.content))
|
|
529
|
+
for key, value in form.entries
|
|
530
|
+
]
|
|
531
|
+
return from_iterable(parts)
|
|
532
|
+
|
|
533
|
+
def _copy(self) -> ServerRequest:
|
|
534
|
+
copy = ClientRequest.__new__(ClientRequest)
|
|
535
|
+
vars(copy).update(
|
|
536
|
+
{k: v for k, v in vars(self).items() if k not in ("cookies", "multipart")}
|
|
537
|
+
)
|
|
538
|
+
return copy
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _raw_bytes(value: object) -> bytes | None:
|
|
542
|
+
if isinstance(value, bytes):
|
|
543
|
+
return value
|
|
544
|
+
if isinstance(value, bytearray):
|
|
545
|
+
return bytes(value)
|
|
546
|
+
if isinstance(value, str):
|
|
547
|
+
return text_encode(value)
|
|
548
|
+
if isinstance(value, UrlParams):
|
|
549
|
+
return text_encode(url_params_to_string(value))
|
|
550
|
+
return None
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def form_data_body(request: ServerRequest) -> _body.FormData | None:
|
|
554
|
+
"""The form of a request made from a client request with a form body."""
|
|
555
|
+
source = request.source
|
|
556
|
+
if not isinstance(source, HttpClientRequest):
|
|
557
|
+
return None
|
|
558
|
+
body = source.body
|
|
559
|
+
if isinstance(body, _body.FormData):
|
|
560
|
+
return body
|
|
561
|
+
if isinstance(body, _body.Raw) and isinstance(body.body, _body.FormData):
|
|
562
|
+
return body.body
|
|
563
|
+
return None
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def from_client_request(request: HttpClientRequest) -> ServerRequest:
|
|
567
|
+
"""A server request made from ``request`` (its full URL as received)."""
|
|
568
|
+
url = request.to_url()
|
|
569
|
+
return ClientRequest(request, request.url if url is None else _url.href(url))
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def _client_body(request: ServerRequest) -> _body.HttpBody:
|
|
573
|
+
if not has_body(request.method):
|
|
574
|
+
return _body.EMPTY
|
|
575
|
+
form = form_data_body(request)
|
|
576
|
+
if form is not None:
|
|
577
|
+
return form
|
|
578
|
+
return _body.stream(
|
|
579
|
+
cast("Stream[bytes, object]", request.stream),
|
|
580
|
+
request.headers.get("content-type"),
|
|
581
|
+
parse_content_length(request.headers.get("content-length")),
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def to_client_request(request: ServerRequest) -> HttpClientRequest:
|
|
586
|
+
"""``request`` as an ``HttpClientRequest`` to its full URL, streaming
|
|
587
|
+
its body."""
|
|
588
|
+
body = _client_body(request)
|
|
589
|
+
headers = request.headers
|
|
590
|
+
if not isinstance(body, _body.Empty) and body.content_length is None:
|
|
591
|
+
headers = headers.remove("content-length")
|
|
592
|
+
url = to_url(request)
|
|
593
|
+
client = HttpClientRequest(request.method, "", EMPTY_URL_PARAMS, None, headers, body)
|
|
594
|
+
# A parsed URL gives its query to ``url_params`` and its fragment to ``hash``.
|
|
595
|
+
return client.set_url(request.url if url is None else url)
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def is_multipart(request: ServerRequest) -> bool:
|
|
599
|
+
content_type = request.headers.get("content-type", "")
|
|
600
|
+
return "multipart/form-data" in content_type.lower() or form_data_body(request) is not None
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
# --- schema helpers ------------------------------------------------------------
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def schema_cookies[T, RD](
|
|
607
|
+
schema: Codec[T, Any, RD, Any], **options: Unpack[ParseOptions]
|
|
608
|
+
) -> Effect[T, SchemaError, RD | HttpServerRequest]:
|
|
609
|
+
"""The request's cookies decoded by ``schema``."""
|
|
610
|
+
decode = decode_unknown_effect(schema, **options)
|
|
611
|
+
return HttpServerRequest().flat_map(lambda request: decode(dict(request.cookies)))
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def schema_headers[T, RD](
|
|
615
|
+
schema: Codec[T, Any, RD, Any], **options: Unpack[ParseOptions]
|
|
616
|
+
) -> Effect[T, SchemaError, RD | HttpServerRequest]:
|
|
617
|
+
"""The request's headers (lowercased names) decoded by ``schema``."""
|
|
618
|
+
decode = decode_unknown_effect(schema, **options)
|
|
619
|
+
return HttpServerRequest().flat_map(lambda request: decode(dict(request.headers)))
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def schema_search_params[T, RD](
|
|
623
|
+
schema: Codec[T, Any, RD, Any], **options: Unpack[ParseOptions]
|
|
624
|
+
) -> Effect[T, SchemaError, RD | ParsedSearchParams]:
|
|
625
|
+
"""The query parameters decoded by ``schema``."""
|
|
626
|
+
decode = decode_unknown_effect(schema, **options)
|
|
627
|
+
return ParsedSearchParams().flat_map(lambda params: decode(dict(params)))
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def schema_body_json[T, RD](
|
|
631
|
+
schema: Codec[T, Any, RD, Any],
|
|
632
|
+
*,
|
|
633
|
+
reviver: Reviver | None = None,
|
|
634
|
+
**options: Unpack[ParseOptions],
|
|
635
|
+
) -> Effect[T, HttpServerError | SchemaError, RD | HttpServerRequest]:
|
|
636
|
+
"""The JSON body decoded by ``schema``."""
|
|
637
|
+
decode = incoming_message.schema_body_json(schema, reviver=reviver, **options)
|
|
638
|
+
return HttpServerRequest().flat_map(decode)
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def schema_body_url_params[T, RD](
|
|
642
|
+
schema: Codec[T, Any, RD, Any], **options: Unpack[ParseOptions]
|
|
643
|
+
) -> Effect[T, HttpServerError | SchemaError, RD | HttpServerRequest]:
|
|
644
|
+
"""The form-encoded body decoded by ``schema``."""
|
|
645
|
+
decode = incoming_message.schema_body_url_params(schema, **options)
|
|
646
|
+
return HttpServerRequest().flat_map(decode)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def schema_body_multipart[T, RD](
|
|
650
|
+
schema: Codec[T, Any, RD, Any], **options: Unpack[ParseOptions]
|
|
651
|
+
) -> Effect[T, Any, RD | HttpServerRequest | Scope | FileSystem | Path]:
|
|
652
|
+
"""The multipart form decoded by ``schema`` (its files persisted)."""
|
|
653
|
+
decode = decode_unknown_effect(schema, **options)
|
|
654
|
+
return HttpServerRequest().flat_map(
|
|
655
|
+
lambda request: request.multipart.flat_map(lambda persisted: decode(dict(persisted)))
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def schema_body_form[T, RD](
|
|
660
|
+
schema: Codec[T, Any, RD, Any], **options: Unpack[ParseOptions]
|
|
661
|
+
) -> Effect[T, Any, RD | HttpServerRequest | Scope | FileSystem | Path]:
|
|
662
|
+
"""A multipart or form-encoded body decoded by ``schema``."""
|
|
663
|
+
decode = decode_unknown_effect(schema, **options)
|
|
664
|
+
url_params = incoming_message.schema_body_url_params(schema, **options)
|
|
665
|
+
|
|
666
|
+
def read(request: ServerRequest) -> Effect[T, Any, Any]:
|
|
667
|
+
if is_multipart(request):
|
|
668
|
+
return request.multipart.flat_map(lambda persisted: decode(dict(persisted)))
|
|
669
|
+
return url_params(request)
|
|
670
|
+
|
|
671
|
+
return HttpServerRequest().flat_map(read)
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def schema_body_form_json[T, RD](
|
|
675
|
+
schema: Codec[T, Any, RD, Any],
|
|
676
|
+
*,
|
|
677
|
+
reviver: Reviver | None = None,
|
|
678
|
+
**options: Unpack[ParseOptions],
|
|
679
|
+
) -> Callable[[str], Effect[T, Any, RD | HttpServerRequest | Scope | FileSystem | Path]]:
|
|
680
|
+
"""A decoder of the JSON text in a form field (multipart or
|
|
681
|
+
form-encoded) by ``schema``, parsed with ``reviver``."""
|
|
682
|
+
from pyfect.http.multipart import schema_json
|
|
683
|
+
|
|
684
|
+
from_multipart = schema_json(schema, reviver=reviver, **options)
|
|
685
|
+
|
|
686
|
+
def field(name: str) -> Effect[T, Any, Any]:
|
|
687
|
+
def read(request: ServerRequest) -> Effect[T, Any, Any]:
|
|
688
|
+
if is_multipart(request):
|
|
689
|
+
return request.multipart.map_error(
|
|
690
|
+
lambda cause: HttpServerError(RequestParseError(request, None, cause))
|
|
691
|
+
).flat_map(lambda persisted: from_multipart(persisted, name))
|
|
692
|
+
return request.url_params_body.flat_map(
|
|
693
|
+
lambda params: from_multipart(cast("Persisted", params.to_record()), name)
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
return HttpServerRequest().flat_map(read)
|
|
697
|
+
|
|
698
|
+
return field
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
__all__ = [
|
|
702
|
+
"BodyTooLargeError",
|
|
703
|
+
"ClientRequest",
|
|
704
|
+
"HttpServerError",
|
|
705
|
+
"HttpServerRequest",
|
|
706
|
+
"MaxBodySize",
|
|
707
|
+
"ParsedSearchParams",
|
|
708
|
+
"RequestProtocol",
|
|
709
|
+
"SchemaError",
|
|
710
|
+
"ServerRequest",
|
|
711
|
+
"SourceRequest",
|
|
712
|
+
"form_data_body",
|
|
713
|
+
"from_client_request",
|
|
714
|
+
"is_multipart",
|
|
715
|
+
"read_limited",
|
|
716
|
+
"remove_host",
|
|
717
|
+
"schema_body_form",
|
|
718
|
+
"schema_body_form_json",
|
|
719
|
+
"schema_body_json",
|
|
720
|
+
"schema_body_multipart",
|
|
721
|
+
"schema_body_url_params",
|
|
722
|
+
"schema_cookies",
|
|
723
|
+
"schema_headers",
|
|
724
|
+
"schema_search_params",
|
|
725
|
+
"search_params_from_url",
|
|
726
|
+
"to_client_request",
|
|
727
|
+
"to_url",
|
|
728
|
+
"to_url_result",
|
|
729
|
+
]
|