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,274 @@
|
|
|
1
|
+
"""Decoding compressed request bodies (Effect's
|
|
2
|
+
``HttpMiddleware.decompression`` and its ``DecompressedRequest``)."""
|
|
3
|
+
# pyright: reportPrivateUsage=false
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import zlib
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from functools import cached_property
|
|
10
|
+
from typing import Any, Literal
|
|
11
|
+
|
|
12
|
+
from pyfect._cached import memoize
|
|
13
|
+
from pyfect._core import Effect, _WithFiber
|
|
14
|
+
from pyfect._fiber import Fiber
|
|
15
|
+
from pyfect._primitives import _Locally
|
|
16
|
+
from pyfect.byte_size import Input as ByteSizeInput
|
|
17
|
+
from pyfect.byte_size import from_input_unsafe, mebibytes
|
|
18
|
+
from pyfect.effect import fail, succeed
|
|
19
|
+
from pyfect.http.headers import Headers
|
|
20
|
+
from pyfect.http.headers import Input as HeadersInput
|
|
21
|
+
from pyfect.http.incoming_message import MaxBodySize
|
|
22
|
+
from pyfect.http.method import HttpMethod, has_body
|
|
23
|
+
from pyfect.http.middleware._types import AppMiddleware
|
|
24
|
+
from pyfect.http.server_error import HttpServerError, RequestParseError, request_parse_error
|
|
25
|
+
from pyfect.http.server_request import HttpServerRequest, RequestProtocol, ServerRequest
|
|
26
|
+
from pyfect.http.server_response import HttpServerResponse, empty
|
|
27
|
+
from pyfect.socket import SocketService
|
|
28
|
+
from pyfect.stream import Stream, from_effect, unwrap
|
|
29
|
+
|
|
30
|
+
type Coding = Literal["gzip", "deflate"]
|
|
31
|
+
|
|
32
|
+
_DEFAULT_MAX = mebibytes(10)
|
|
33
|
+
_WBITS: dict[Coding, int] = {"gzip": 31, "deflate": 15}
|
|
34
|
+
MAX_CODINGS = 2
|
|
35
|
+
"""How many codings may be stacked on one body."""
|
|
36
|
+
_PIECE = 64 * 1024
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def parse_content_encoding(header: str) -> tuple[Coding, ...] | None:
|
|
40
|
+
"""The codings of a ``content-encoding`` header in the order applied
|
|
41
|
+
(``identity`` dropped); ``None`` when one is not supported or more than
|
|
42
|
+
``MAX_CODINGS`` are stacked."""
|
|
43
|
+
codings: list[Coding] = []
|
|
44
|
+
for member in header.split(","):
|
|
45
|
+
coding = member.strip().lower()
|
|
46
|
+
if coding in ("", "identity"):
|
|
47
|
+
continue
|
|
48
|
+
if coding not in ("gzip", "deflate"):
|
|
49
|
+
return None
|
|
50
|
+
codings.append("gzip" if coding == "gzip" else "deflate")
|
|
51
|
+
return tuple(codings) if len(codings) <= MAX_CODINGS else None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _TooLarge(Exception):
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class _Decoders:
|
|
59
|
+
"""Stacked decoders that inflate at most ``_PIECE`` bytes per call, each
|
|
60
|
+
coding's total output counted against ``limit``: a bomb fails before
|
|
61
|
+
more than about ``limit`` bytes exist."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, codings: tuple[Coding, ...], limit: int) -> None:
|
|
64
|
+
# The coding applied last is decoded first.
|
|
65
|
+
self._decoders = [zlib.decompressobj(_WBITS[coding]) for coding in reversed(codings)]
|
|
66
|
+
self._produced = [0] * len(self._decoders)
|
|
67
|
+
self._limit = limit
|
|
68
|
+
|
|
69
|
+
def feed(self, data: bytes) -> bytes:
|
|
70
|
+
output: list[bytes] = []
|
|
71
|
+
self._run(0, data, output)
|
|
72
|
+
return b"".join(output)
|
|
73
|
+
|
|
74
|
+
def finish(self) -> bytes | None:
|
|
75
|
+
"""The rest of the body; ``None`` when a coding is truncated."""
|
|
76
|
+
output: list[bytes] = []
|
|
77
|
+
for level, decoder in enumerate(self._decoders):
|
|
78
|
+
self._emit(level, decoder.flush(), output)
|
|
79
|
+
if not decoder.eof:
|
|
80
|
+
return None
|
|
81
|
+
return b"".join(output)
|
|
82
|
+
|
|
83
|
+
def _run(self, level: int, data: bytes, output: list[bytes]) -> None:
|
|
84
|
+
decoder = self._decoders[level]
|
|
85
|
+
while True:
|
|
86
|
+
piece = decoder.decompress(data, _PIECE)
|
|
87
|
+
data = decoder.unconsumed_tail
|
|
88
|
+
self._emit(level, piece, output)
|
|
89
|
+
if not data and len(piece) < _PIECE:
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
def _emit(self, level: int, piece: bytes, output: list[bytes]) -> None:
|
|
93
|
+
self._produced[level] += len(piece)
|
|
94
|
+
if self._produced[level] > self._limit:
|
|
95
|
+
raise _TooLarge
|
|
96
|
+
if level + 1 < len(self._decoders):
|
|
97
|
+
self._run(level + 1, piece, output)
|
|
98
|
+
elif piece:
|
|
99
|
+
output.append(piece)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class DecompressedRequest(ServerRequest):
|
|
103
|
+
"""``inner`` with its body decoded, without its ``content-encoding``
|
|
104
|
+
and ``content-length``. The decoded body may be at most
|
|
105
|
+
``max_decompressed_size`` (else ``MaxBodySize``, else 10 MiB) bytes."""
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self, inner: ServerRequest, codings: tuple[Coding, ...], max_decompressed_size: int | None
|
|
109
|
+
) -> None:
|
|
110
|
+
super().__init__(None, inner.headers.remove_many(("content-encoding", "content-length")))
|
|
111
|
+
self.inner = inner
|
|
112
|
+
self.codings = codings
|
|
113
|
+
self.max_decompressed_size = max_decompressed_size
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def source(self) -> object:
|
|
117
|
+
return self.inner.source
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def original_url(self) -> str:
|
|
121
|
+
return self.inner.original_url
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def url(self) -> str:
|
|
125
|
+
return self.inner.url
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def method(self) -> HttpMethod:
|
|
129
|
+
return self.inner.method
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def remote_address(self) -> str | None:
|
|
133
|
+
return self.inner.remote_address
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def peer_address(self) -> str | None:
|
|
137
|
+
return self.inner.peer_address
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def protocol(self) -> RequestProtocol | None:
|
|
141
|
+
return self.inner.protocol
|
|
142
|
+
|
|
143
|
+
def _source_headers(self) -> Headers:
|
|
144
|
+
return self.inner.headers
|
|
145
|
+
|
|
146
|
+
def _copy(self) -> ServerRequest:
|
|
147
|
+
return DecompressedRequest(self.inner, self.codings, self.max_decompressed_size)
|
|
148
|
+
|
|
149
|
+
def modify(
|
|
150
|
+
self,
|
|
151
|
+
*,
|
|
152
|
+
url: str | None = None,
|
|
153
|
+
headers: Headers | None = None,
|
|
154
|
+
remote_address: Any = ...,
|
|
155
|
+
peer_address: str | None = None,
|
|
156
|
+
protocol: RequestProtocol | None = None,
|
|
157
|
+
) -> ServerRequest:
|
|
158
|
+
options: dict[str, Any] = {
|
|
159
|
+
"url": url,
|
|
160
|
+
"headers": headers,
|
|
161
|
+
"peer_address": peer_address,
|
|
162
|
+
"protocol": protocol,
|
|
163
|
+
}
|
|
164
|
+
if remote_address is not ...:
|
|
165
|
+
options["remote_address"] = remote_address
|
|
166
|
+
return DecompressedRequest(
|
|
167
|
+
self.inner.modify(**options), self.codings, self.max_decompressed_size
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def upgrade_with(
|
|
171
|
+
self, *, headers: HeadersInput | None = None
|
|
172
|
+
) -> Effect[SocketService, HttpServerError]:
|
|
173
|
+
return self.inner.upgrade_with(headers=headers)
|
|
174
|
+
|
|
175
|
+
def _error(
|
|
176
|
+
self,
|
|
177
|
+
cause: object,
|
|
178
|
+
description: str | None = None,
|
|
179
|
+
kind: Literal["Parse", "BodyTooLarge"] = "Parse",
|
|
180
|
+
) -> HttpServerError:
|
|
181
|
+
return request_parse_error(self, description, cause, kind)
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def stream(self) -> Stream[bytes, HttpServerError]:
|
|
185
|
+
"""Read from the inner body only once pulled."""
|
|
186
|
+
|
|
187
|
+
def make(fiber: Fiber[Any, Any]) -> Effect[Stream[bytes, HttpServerError]]:
|
|
188
|
+
limit = self.max_decompressed_size
|
|
189
|
+
if limit is None:
|
|
190
|
+
limit = fiber.get_ref(MaxBodySize)
|
|
191
|
+
if limit is None:
|
|
192
|
+
limit = _DEFAULT_MAX
|
|
193
|
+
decoders = _Decoders(self.codings, limit)
|
|
194
|
+
|
|
195
|
+
def decode(step: Callable[[], bytes | None]) -> Effect[bytes, HttpServerError]:
|
|
196
|
+
try:
|
|
197
|
+
data = step()
|
|
198
|
+
except zlib.error as cause:
|
|
199
|
+
return fail(self._error(cause))
|
|
200
|
+
except _TooLarge:
|
|
201
|
+
description = f"Decompressed request body exceeds {limit} bytes"
|
|
202
|
+
return fail(self._error(None, description, "BodyTooLarge"))
|
|
203
|
+
if data is None:
|
|
204
|
+
return fail(self._error(None, "Truncated compressed body"))
|
|
205
|
+
return succeed(data)
|
|
206
|
+
|
|
207
|
+
def feed(chunk: bytes) -> Effect[bytes, HttpServerError]:
|
|
208
|
+
return decode(lambda: decoders.feed(chunk))
|
|
209
|
+
|
|
210
|
+
def finish() -> Effect[bytes, HttpServerError]:
|
|
211
|
+
return decode(decoders.finish)
|
|
212
|
+
|
|
213
|
+
decoded = self.inner.stream.map_effect(feed).concat(
|
|
214
|
+
from_effect(_WithFiber(lambda _: finish()))
|
|
215
|
+
)
|
|
216
|
+
return succeed(decoded.filter(lambda chunk: len(chunk) > 0))
|
|
217
|
+
|
|
218
|
+
return unwrap(_WithFiber(make))
|
|
219
|
+
|
|
220
|
+
@cached_property
|
|
221
|
+
def _bytes(self) -> Effect[bytes, HttpServerError]:
|
|
222
|
+
return memoize(self.stream.run_collect().map(b"".join))
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def bytes_(self) -> Effect[bytes, HttpServerError]:
|
|
226
|
+
return self._bytes
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def json(self) -> Effect[Any, HttpServerError]:
|
|
230
|
+
return self.text.flat_map(
|
|
231
|
+
lambda text: succeed(None) if text == "" else self._parse_json(text)
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def multipart_stream(self) -> Stream[Any, Any]:
|
|
236
|
+
from pyfect.http.multipart import MultipartError, make_channel
|
|
237
|
+
|
|
238
|
+
def convert(cause: HttpServerError) -> MultipartError:
|
|
239
|
+
# Failures of this request come from the client's data; those of
|
|
240
|
+
# the inner request are transport errors.
|
|
241
|
+
reason = cause.reason
|
|
242
|
+
if isinstance(reason, RequestParseError) and reason.request is self:
|
|
243
|
+
return MultipartError.from_reason(reason.kind, cause)
|
|
244
|
+
return MultipartError.from_reason("InternalError", cause)
|
|
245
|
+
|
|
246
|
+
return self.stream.map_error(convert).pipe_through_channel(make_channel(self.headers))
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def decompression(
|
|
250
|
+
*, max_decompressed_size: ByteSizeInput | None = None
|
|
251
|
+
) -> AppMiddleware[HttpServerRequest]:
|
|
252
|
+
"""Middleware decoding gzip and deflate request bodies; an unsupported
|
|
253
|
+
coding is answered with 415 before the app runs."""
|
|
254
|
+
limit = None if max_decompressed_size is None else from_input_unsafe(max_decompressed_size)
|
|
255
|
+
|
|
256
|
+
def middleware[E, R](
|
|
257
|
+
app: Effect[HttpServerResponse, E, R],
|
|
258
|
+
) -> Effect[HttpServerResponse, E, R | HttpServerRequest]:
|
|
259
|
+
def body(fiber: Fiber[Any, Any]) -> Effect[HttpServerResponse, E, R]:
|
|
260
|
+
request = fiber._context.get_unsafe(HttpServerRequest)
|
|
261
|
+
encoding = request.headers.get("content-encoding")
|
|
262
|
+
if encoding is None or not has_body(request.method):
|
|
263
|
+
return app
|
|
264
|
+
codings = parse_content_encoding(encoding)
|
|
265
|
+
if codings is None:
|
|
266
|
+
return succeed(empty(status=415, headers={"accept-encoding": "gzip, deflate"}))
|
|
267
|
+
if not codings:
|
|
268
|
+
return app
|
|
269
|
+
decompressed = DecompressedRequest(request, codings, limit)
|
|
270
|
+
return _Locally(app, lambda ctx: ctx.add(HttpServerRequest, decompressed))
|
|
271
|
+
|
|
272
|
+
return _WithFiber(body)
|
|
273
|
+
|
|
274
|
+
return middleware
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Trusting ``X-Forwarded-*`` headers from known proxies (Effect's
|
|
2
|
+
``HttpMiddleware.xForwardedHeaders``)."""
|
|
3
|
+
# pyright: reportPrivateUsage=false
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from collections.abc import Callable, Sequence
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from pyfect._core import Effect, _WithFiber
|
|
11
|
+
from pyfect._fiber import Fiber
|
|
12
|
+
from pyfect._primitives import _Locally
|
|
13
|
+
from pyfect.http._authority import parse_authority
|
|
14
|
+
from pyfect.http.middleware._types import AppMiddleware
|
|
15
|
+
from pyfect.http.server_request import HttpServerRequest, ServerRequest
|
|
16
|
+
from pyfect.http.server_response import HttpServerResponse
|
|
17
|
+
from pyfect.net import ip_network
|
|
18
|
+
from pyfect.net.ip_network import IpNetwork
|
|
19
|
+
from pyfect.net.net_address import (
|
|
20
|
+
IpAddress,
|
|
21
|
+
format_ip,
|
|
22
|
+
inet_address_from_string,
|
|
23
|
+
ip_from_string,
|
|
24
|
+
to_canonical,
|
|
25
|
+
)
|
|
26
|
+
from pyfect.result import Ok
|
|
27
|
+
|
|
28
|
+
type TrustedProxies = Sequence[IpNetwork] | Callable[[IpAddress], bool]
|
|
29
|
+
"""The proxies whose forwarded headers are believed: networks, or a test."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _split(value: str | None) -> list[str] | None:
|
|
33
|
+
return None if value is None else [part.strip() for part in value.split(",")]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _aligned(header: str | None, length: int, index: int) -> str | None:
|
|
37
|
+
"""A host or proto list belongs to the hops only with one value per
|
|
38
|
+
``X-Forwarded-For`` entry."""
|
|
39
|
+
values = _split(header)
|
|
40
|
+
return values[index] if values is not None and len(values) == length else None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _address(text: str | None) -> IpAddress | None:
|
|
44
|
+
if text is None:
|
|
45
|
+
return None
|
|
46
|
+
bare = text[1:-1] if text.startswith("[") and text.endswith("]") else text
|
|
47
|
+
match ip_from_string(bare):
|
|
48
|
+
case Ok(ip):
|
|
49
|
+
return to_canonical(ip)
|
|
50
|
+
case _:
|
|
51
|
+
pass
|
|
52
|
+
match inet_address_from_string(text):
|
|
53
|
+
case Ok(inet):
|
|
54
|
+
return to_canonical(inet.address)
|
|
55
|
+
case _:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _apply(request: ServerRequest, trusted: Callable[[IpAddress], bool]) -> ServerRequest:
|
|
60
|
+
peer = _address(request.peer_address)
|
|
61
|
+
if peer is None or not trusted(peer):
|
|
62
|
+
return request
|
|
63
|
+
addresses = _split(request.headers.get("x-forwarded-for"))
|
|
64
|
+
if addresses is None:
|
|
65
|
+
return request
|
|
66
|
+
for index in range(len(addresses) - 1, -1, -1):
|
|
67
|
+
address = _address(addresses[index])
|
|
68
|
+
if address is None:
|
|
69
|
+
return request
|
|
70
|
+
if index > 0 and trusted(address):
|
|
71
|
+
continue
|
|
72
|
+
host = _aligned(request.headers.get("x-forwarded-host"), len(addresses), index)
|
|
73
|
+
protocol = _aligned(request.headers.get("x-forwarded-proto"), len(addresses), index)
|
|
74
|
+
protocol = None if protocol is None else protocol.lower()
|
|
75
|
+
return request.modify(
|
|
76
|
+
remote_address=format_ip(address),
|
|
77
|
+
peer_address=request.peer_address,
|
|
78
|
+
headers=request.headers.set("host", host)
|
|
79
|
+
if host is not None and parse_authority(host) is not None
|
|
80
|
+
else None,
|
|
81
|
+
protocol="https" if protocol == "https" else "http" if protocol == "http" else None,
|
|
82
|
+
)
|
|
83
|
+
return request
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def x_forwarded_headers(
|
|
87
|
+
trusted_proxies: TrustedProxies,
|
|
88
|
+
) -> AppMiddleware[HttpServerRequest]:
|
|
89
|
+
"""Middleware taking the client address (and its aligned
|
|
90
|
+
``X-Forwarded-Host`` / ``-Proto``) from ``X-Forwarded-For`` when the
|
|
91
|
+
transport peer is a trusted proxy: the rightmost hop that is not one,
|
|
92
|
+
stopping at an entry that is not an address."""
|
|
93
|
+
trusted: Callable[[IpAddress], bool]
|
|
94
|
+
if callable(trusted_proxies):
|
|
95
|
+
trusted = trusted_proxies
|
|
96
|
+
else:
|
|
97
|
+
networks = tuple(trusted_proxies)
|
|
98
|
+
|
|
99
|
+
def in_networks(address: IpAddress) -> bool:
|
|
100
|
+
return any(ip_network.contains(network, address) for network in networks)
|
|
101
|
+
|
|
102
|
+
trusted = in_networks
|
|
103
|
+
|
|
104
|
+
def middleware[E, R](
|
|
105
|
+
app: Effect[HttpServerResponse, E, R],
|
|
106
|
+
) -> Effect[HttpServerResponse, E, R | HttpServerRequest]:
|
|
107
|
+
def body(fiber: Fiber[Any, Any]) -> Effect[HttpServerResponse, E, R]:
|
|
108
|
+
request = _apply(fiber._context.get_unsafe(HttpServerRequest), trusted)
|
|
109
|
+
return _Locally(app, lambda ctx: ctx.add(HttpServerRequest, request))
|
|
110
|
+
|
|
111
|
+
return _WithFiber(body)
|
|
112
|
+
|
|
113
|
+
return middleware
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Logging each response (Effect's ``HttpMiddleware.logger``)."""
|
|
2
|
+
# pyright: reportPrivateUsage=false
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pyfect._core import Effect, _WithFiber
|
|
9
|
+
from pyfect._effect_args import require_effect
|
|
10
|
+
from pyfect._fiber import Fiber
|
|
11
|
+
from pyfect.cause import Interrupt
|
|
12
|
+
from pyfect.effect import from_exit
|
|
13
|
+
from pyfect.exit import Exit, Failure
|
|
14
|
+
from pyfect.http import _request_state
|
|
15
|
+
from pyfect.http.server_error import ClientAbort, cause_response_stripped
|
|
16
|
+
from pyfect.http.server_request import HttpServerRequest
|
|
17
|
+
from pyfect.http.server_response import HttpServerResponse
|
|
18
|
+
from pyfect.logger import log
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def strip_search_and_hash(url: str) -> str:
|
|
22
|
+
"""``url`` without its query and fragment."""
|
|
23
|
+
ends = [index for index in (url.find("?"), url.find("#")) if index != -1]
|
|
24
|
+
return url[: min(ends)] if ends else url
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def with_logger_disabled[A, E, R](eff: Effect[A, E, R]) -> Effect[A, E, R | HttpServerRequest]:
|
|
28
|
+
"""``eff``, and the request it runs in is not logged."""
|
|
29
|
+
require_effect(eff, "eff")
|
|
30
|
+
|
|
31
|
+
def body(fiber: Fiber[Any, Any]) -> Effect[A, E, R]:
|
|
32
|
+
state = _request_state.get(fiber._context)
|
|
33
|
+
if state is not None:
|
|
34
|
+
state.logger_disabled = True
|
|
35
|
+
return eff
|
|
36
|
+
|
|
37
|
+
return _WithFiber(body)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def logger[E, R](
|
|
41
|
+
app: Effect[HttpServerResponse, E, R],
|
|
42
|
+
) -> Effect[HttpServerResponse, E, R | HttpServerRequest]:
|
|
43
|
+
"""Log ``"Sent HTTP response"`` (or the failure's cause) with the
|
|
44
|
+
method, path and status, in an ``http.span`` log span."""
|
|
45
|
+
require_effect(app, "app")
|
|
46
|
+
|
|
47
|
+
def body(fiber: Fiber[Any, Any]) -> Effect[HttpServerResponse, E, R | HttpServerRequest]:
|
|
48
|
+
request = fiber._context.get_unsafe(HttpServerRequest)
|
|
49
|
+
state = _request_state.get(fiber._context)
|
|
50
|
+
path = strip_search_and_hash(request.url)
|
|
51
|
+
|
|
52
|
+
def after(exit: Exit[HttpServerResponse, E]) -> Effect[HttpServerResponse, E]:
|
|
53
|
+
if state is not None and state.logger_disabled:
|
|
54
|
+
return from_exit(exit)
|
|
55
|
+
message: object = "Sent HTTP response"
|
|
56
|
+
if isinstance(exit, Failure):
|
|
57
|
+
response, cause = cause_response_stripped(exit.cause)
|
|
58
|
+
if cause is not None and not all(
|
|
59
|
+
isinstance(reason, Interrupt) and ClientAbort in reason.annotations
|
|
60
|
+
for reason in cause.reasons
|
|
61
|
+
):
|
|
62
|
+
message = cause
|
|
63
|
+
status = response.status
|
|
64
|
+
else:
|
|
65
|
+
status = exit.value.status
|
|
66
|
+
logged = log(message).annotate_logs(
|
|
67
|
+
{"http.method": request.method, "http.url": path, "http.status": status}
|
|
68
|
+
)
|
|
69
|
+
return logged.and_then(from_exit(exit))
|
|
70
|
+
|
|
71
|
+
return app.exit().flat_map(after).with_log_span("http.span")
|
|
72
|
+
|
|
73
|
+
return _WithFiber(body)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Parsing the query once for the app (Effect's
|
|
2
|
+
``HttpMiddleware.searchParamsParser``)."""
|
|
3
|
+
# pyright: reportPrivateUsage=false
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from pyfect._core import Effect, _WithFiber
|
|
10
|
+
from pyfect._effect_args import require_effect
|
|
11
|
+
from pyfect._fiber import Fiber
|
|
12
|
+
from pyfect._primitives import _Locally
|
|
13
|
+
from pyfect.http.server_request import (
|
|
14
|
+
HttpServerRequest,
|
|
15
|
+
ParsedSearchParams,
|
|
16
|
+
search_params_from_url,
|
|
17
|
+
)
|
|
18
|
+
from pyfect.http.server_response import HttpServerResponse
|
|
19
|
+
from pyfect.http.url import parse
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def search_params_parser[E, R](
|
|
23
|
+
app: Effect[HttpServerResponse, E, R | ParsedSearchParams],
|
|
24
|
+
) -> Effect[HttpServerResponse, E, R | HttpServerRequest]:
|
|
25
|
+
"""``app`` with ``ParsedSearchParams``: the request URL's query as a
|
|
26
|
+
record (a repeated key gives a list)."""
|
|
27
|
+
require_effect(app, "app")
|
|
28
|
+
|
|
29
|
+
def body(fiber: Fiber[Any, Any]) -> Effect[HttpServerResponse, E, R]:
|
|
30
|
+
request = fiber._context.get_unsafe(HttpServerRequest)
|
|
31
|
+
params = search_params_from_url(parse(request.original_url, "http://localhost"))
|
|
32
|
+
return _Locally(app, lambda ctx: ctx.add(ParsedSearchParams, params))
|
|
33
|
+
|
|
34
|
+
return _WithFiber(body)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""The server span of a request (Effect's ``HttpMiddleware.tracer``)."""
|
|
2
|
+
# pyright: reportPrivateUsage=false
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections.abc import Callable, Sequence
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
from pyfect._core import Effect, _WithFiber
|
|
10
|
+
from pyfect._effect_args import require_effect
|
|
11
|
+
from pyfect._fiber import Fiber
|
|
12
|
+
from pyfect._primitives import Restore, _Locally, uninterruptible_mask
|
|
13
|
+
from pyfect._tracer_ref import CurrentTracer
|
|
14
|
+
from pyfect.cause import fail
|
|
15
|
+
from pyfect.clock import Clock
|
|
16
|
+
from pyfect.context import Reference
|
|
17
|
+
from pyfect.effect import sync
|
|
18
|
+
from pyfect.exit import Exit, Failure, Success
|
|
19
|
+
from pyfect.http import headers as _headers
|
|
20
|
+
from pyfect.http import trace_context
|
|
21
|
+
from pyfect.http import url_params as _url_params
|
|
22
|
+
from pyfect.http.client._make import _add_header_attributes, _add_url_attributes
|
|
23
|
+
from pyfect.http.server_error import (
|
|
24
|
+
HttpServerError,
|
|
25
|
+
ResponseError,
|
|
26
|
+
cause_response_stripped,
|
|
27
|
+
)
|
|
28
|
+
from pyfect.http.server_request import HttpServerRequest, ServerRequest, to_url
|
|
29
|
+
from pyfect.http.server_response import HttpServerResponse
|
|
30
|
+
from pyfect.layer import Layer
|
|
31
|
+
from pyfect.layer import succeed as layer_succeed
|
|
32
|
+
from pyfect.tracer import ParentSpan, Span, TracerEnabled, native_tracer
|
|
33
|
+
from pyfect.tracer._model import SpanOptions
|
|
34
|
+
from pyfect.tracer._spans import make_span_unsafe
|
|
35
|
+
|
|
36
|
+
type Phase = Literal["request", "response"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TracerDisabledWhen(Reference[Callable[[ServerRequest], bool]]):
|
|
40
|
+
"""Requests handled without a server span."""
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def default(cls) -> Callable[[ServerRequest], bool]:
|
|
44
|
+
return lambda _request: False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def layer_tracer_disabled_for_urls(urls: Sequence[str]) -> Layer[TracerDisabledWhen]:
|
|
48
|
+
"""No spans for requests to exactly these URLs."""
|
|
49
|
+
disabled = tuple(urls)
|
|
50
|
+
return layer_succeed(TracerDisabledWhen, lambda request: request.url in disabled)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SpanNameGenerator(Reference[Callable[[ServerRequest], str]]):
|
|
54
|
+
"""The name of a request's span; its method by default."""
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def default(cls) -> Callable[[ServerRequest], str]:
|
|
58
|
+
return lambda request: request.method
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TracerHeaderFilter(Reference[Callable[[str, Phase], bool]]):
|
|
62
|
+
"""The headers recorded as span attributes, by name and phase (none by
|
|
63
|
+
default)."""
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def default(cls) -> Callable[[str, Phase], bool]:
|
|
67
|
+
return lambda _name, _phase: False
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def is_tracer_disabled_unsafe(fiber: Fiber[Any, Any], request: ServerRequest) -> bool:
|
|
71
|
+
return not fiber.get_ref(TracerEnabled) or fiber.get_ref(TracerDisabledWhen)(request)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def uses_native_tracer(fiber: Fiber[Any, Any]) -> bool:
|
|
75
|
+
"""The native tracer keeps spans nobody can observe."""
|
|
76
|
+
return fiber.get_ref(CurrentTracer) is native_tracer
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _response_span_exit(
|
|
80
|
+
request: ServerRequest, response: HttpServerResponse
|
|
81
|
+
) -> Exit[HttpServerResponse, HttpServerError]:
|
|
82
|
+
# OpenTelemetry fails server spans of 5xx responses.
|
|
83
|
+
if not 500 <= response.status < 600:
|
|
84
|
+
return Success(response)
|
|
85
|
+
return Failure(fail(HttpServerError(ResponseError(request, response))))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _end(fiber: Fiber[Any, Any], span: Span, request: ServerRequest, exit: Exit[Any, Any]) -> None:
|
|
89
|
+
end_time = fiber.get_ref(Clock).now_nanos_unsafe()
|
|
90
|
+
if isinstance(exit, Success) and (
|
|
91
|
+
not span.sampled or fiber.get_ref(CurrentTracer) is native_tracer
|
|
92
|
+
):
|
|
93
|
+
span.end(end_time, _response_span_exit(request, exit.value))
|
|
94
|
+
return
|
|
95
|
+
redacted_headers = fiber.get_ref(_headers.CurrentRedactedNames)
|
|
96
|
+
accept = fiber.get_ref(TracerHeaderFilter)
|
|
97
|
+
span_exit: Exit[Any, Any]
|
|
98
|
+
if isinstance(exit, Failure):
|
|
99
|
+
response, cause = cause_response_stripped(exit.cause)
|
|
100
|
+
# Only 5xx responses fail a server span; interrupts still do.
|
|
101
|
+
if cause is not None and (response.status >= 500 or cause.has_interrupts):
|
|
102
|
+
span_exit = Failure(cause)
|
|
103
|
+
else:
|
|
104
|
+
span_exit = _response_span_exit(request, response)
|
|
105
|
+
else:
|
|
106
|
+
response = exit.value
|
|
107
|
+
span_exit = _response_span_exit(request, response)
|
|
108
|
+
if span.sampled:
|
|
109
|
+
span.attribute("http.request.method", request.method)
|
|
110
|
+
url = to_url(request)
|
|
111
|
+
if url is not None:
|
|
112
|
+
_add_url_attributes(span, url, fiber.get_ref(_url_params.CurrentRedactedNames))
|
|
113
|
+
user_agent = request.headers.get("user-agent")
|
|
114
|
+
if user_agent is not None:
|
|
115
|
+
span.attribute("user_agent.original", user_agent)
|
|
116
|
+
_add_header_attributes(span, "request", request.headers, accept, redacted_headers)
|
|
117
|
+
if request.remote_address is not None:
|
|
118
|
+
span.attribute("client.address", request.remote_address)
|
|
119
|
+
span.attribute("http.response.status_code", response.status)
|
|
120
|
+
_add_header_attributes(span, "response", response.headers, accept, redacted_headers)
|
|
121
|
+
span.end(end_time, span_exit)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def tracer[E, R](
|
|
125
|
+
app: Effect[HttpServerResponse, E, R],
|
|
126
|
+
) -> Effect[HttpServerResponse, E, R | HttpServerRequest]:
|
|
127
|
+
"""Run ``app`` in a server span named by ``SpanNameGenerator``, a child
|
|
128
|
+
of the span in the request's trace headers, with the HTTP attributes of
|
|
129
|
+
the request and response."""
|
|
130
|
+
require_effect(app, "app")
|
|
131
|
+
|
|
132
|
+
def body(
|
|
133
|
+
fiber: Fiber[Any, Any], restore: Restore
|
|
134
|
+
) -> Effect[HttpServerResponse, E, R | HttpServerRequest]:
|
|
135
|
+
request = fiber._context.get_unsafe(HttpServerRequest)
|
|
136
|
+
if is_tracer_disabled_unsafe(fiber, request):
|
|
137
|
+
return restore(app)
|
|
138
|
+
parent = trace_context.from_headers(request.headers)
|
|
139
|
+
options: SpanOptions = {"kind": "server"}
|
|
140
|
+
if parent is not None:
|
|
141
|
+
options["parent"] = parent
|
|
142
|
+
span = make_span_unsafe(fiber, fiber.get_ref(SpanNameGenerator)(request), options)
|
|
143
|
+
|
|
144
|
+
def finish(exit: Exit[HttpServerResponse, E]) -> Effect[None]:
|
|
145
|
+
return sync(lambda: _end(fiber, span, request, exit))
|
|
146
|
+
|
|
147
|
+
inner: Effect[HttpServerResponse, E, R | HttpServerRequest] = _Locally(
|
|
148
|
+
app, lambda ctx: ctx.add(ParentSpan, span)
|
|
149
|
+
)
|
|
150
|
+
return restore(inner).on_exit(finish)
|
|
151
|
+
|
|
152
|
+
# The span is made and its end registered in one uninterruptible step.
|
|
153
|
+
return uninterruptible_mask(lambda restore: _WithFiber(lambda fiber: body(fiber, restore)))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""The type of a configured middleware (``cors(...)``, ``compression(...)``):
|
|
2
|
+
generic in the app it wraps, so the app's errors and requirements pass
|
|
3
|
+
through, with ``RM`` the requirements the middleware adds."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Protocol
|
|
8
|
+
|
|
9
|
+
from pyfect._core import Effect
|
|
10
|
+
from pyfect.http.server_response import HttpServerResponse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AppMiddleware[RM](Protocol):
|
|
14
|
+
def __call__[E, R](
|
|
15
|
+
self, app: Effect[HttpServerResponse, E, R], /
|
|
16
|
+
) -> Effect[HttpServerResponse, E, R | RM]: ...
|