pyfect-dev 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyfect/__init__.py +729 -0
- pyfect/_all.py +242 -0
- pyfect/_background.py +28 -0
- pyfect/_binary_codecs.py +124 -0
- pyfect/_cached.py +165 -0
- pyfect/_catch.py +128 -0
- pyfect/_catch_reason.py +574 -0
- pyfect/_concurrency.py +291 -0
- pyfect/_context_effects.py +163 -0
- pyfect/_context_managers.py +76 -0
- pyfect/_core.py +368 -0
- pyfect/_digits.py +71 -0
- pyfect/_effect_args.py +46 -0
- pyfect/_effect_base.py +71 -0
- pyfect/_effect_concurrency.py +133 -0
- pyfect/_effect_eager.py +103 -0
- pyfect/_effect_errors.py +344 -0
- pyfect/_effect_folding.py +110 -0
- pyfect/_effect_hash.py +140 -0
- pyfect/_effect_logging.py +57 -0
- pyfect/_effect_metrics.py +102 -0
- pyfect/_effect_resources.py +179 -0
- pyfect/_effect_services.py +25 -0
- pyfect/_effect_timing.py +199 -0
- pyfect/_effect_tracing.py +99 -0
- pyfect/_equal.py +195 -0
- pyfect/_extras.py +43 -0
- pyfect/_fiber.py +969 -0
- pyfect/_fiber_all.py +134 -0
- pyfect/_fiber_collections.py +106 -0
- pyfect/_fn.py +706 -0
- pyfect/_hamt.py +174 -0
- pyfect/_interrupt_reason.py +33 -0
- pyfect/_isaac.py +149 -0
- pyfect/_iterables.py +231 -0
- pyfect/_js.py +263 -0
- pyfect/_js_date.py +87 -0
- pyfect/_json_text.py +286 -0
- pyfect/_json_value.py +9 -0
- pyfect/_layer_core.py +403 -0
- pyfect/_layer_for_life.py +33 -0
- pyfect/_layer_tracing.py +70 -0
- pyfect/_locked_ref.py +147 -0
- pyfect/_loop_thread.py +152 -0
- pyfect/_metric_ref.py +29 -0
- pyfect/_one_or_many.py +14 -0
- pyfect/_os_wait.py +118 -0
- pyfect/_path_posix.py +129 -0
- pyfect/_primitives.py +434 -0
- pyfect/_public.py +36 -0
- pyfect/_reasons.py +64 -0
- pyfect/_released.py +67 -0
- pyfect/_root.py +87 -0
- pyfect/_round_robin.py +73 -0
- pyfect/_some.py +17 -0
- pyfect/_stack.py +52 -0
- pyfect/_stack_frame.py +114 -0
- pyfect/_string_hash.py +35 -0
- pyfect/_subscriber_queue.py +47 -0
- pyfect/_tags.py +14 -0
- pyfect/_tracer_ref.py +21 -0
- pyfect/_tx_ops.py +77 -0
- pyfect/_unsubscripted.py +23 -0
- pyfect/_url.py +54 -0
- pyfect/_waiters.py +127 -0
- pyfect/_wake_tracking.py +98 -0
- pyfect/_wire_codec.py +123 -0
- pyfect/ai/__init__.py +73 -0
- pyfect/ai/_approvals.py +249 -0
- pyfect/ai/_decision_answers.py +185 -0
- pyfect/ai/_generated_client.py +298 -0
- pyfect/ai/_generated_support.py +84 -0
- pyfect/ai/_mcp/__init__.py +1 -0
- pyfect/ai/_mcp/batch.py +68 -0
- pyfect/ai/_mcp/client_transport.py +412 -0
- pyfect/ai/_mcp/client_wire.py +284 -0
- pyfect/ai/_mcp/core.py +490 -0
- pyfect/ai/_mcp/dispatch.py +1048 -0
- pyfect/ai/_mcp/http.py +462 -0
- pyfect/ai/_mcp/http_admission.py +269 -0
- pyfect/ai/_mcp/http_status.py +27 -0
- pyfect/ai/_mcp/input_wire.py +226 -0
- pyfect/ai/_mcp/log_levels.py +55 -0
- pyfect/ai/_mcp/param_headers.py +63 -0
- pyfect/ai/_mcp/projection.py +248 -0
- pyfect/ai/_mcp/register.py +683 -0
- pyfect/ai/_mcp/reverse_policy.py +155 -0
- pyfect/ai/_mcp/routing_header.py +24 -0
- pyfect/ai/_mcp/sessions.py +114 -0
- pyfect/ai/_mcp/stdio.py +110 -0
- pyfect/ai/_mcp/subscriptions.py +134 -0
- pyfect/ai/_mcp/tool_schema.py +35 -0
- pyfect/ai/_noul_answers.py +64 -0
- pyfect/ai/_openai_all_of.py +137 -0
- pyfect/ai/_openai_errors.py +233 -0
- pyfect/ai/_provider.py +439 -0
- pyfect/ai/_provider_tools.py +129 -0
- pyfect/ai/_stream_resolution.py +231 -0
- pyfect/ai/_structured_output_checks.py +138 -0
- pyfect/ai/_structured_output_codec.py +229 -0
- pyfect/ai/_structured_output_json.py +109 -0
- pyfect/ai/_tool_call_ids.py +80 -0
- pyfect/ai/ai_error.py +785 -0
- pyfect/ai/anthropic/__init__.py +28 -0
- pyfect/ai/anthropic/_errors.py +164 -0
- pyfect/ai/anthropic/_prompt.py +375 -0
- pyfect/ai/anthropic/_response.py +443 -0
- pyfect/ai/anthropic/_schema.py +206 -0
- pyfect/ai/anthropic/_stream.py +377 -0
- pyfect/ai/anthropic/_tools.py +248 -0
- pyfect/ai/anthropic/client.py +289 -0
- pyfect/ai/anthropic/config.py +32 -0
- pyfect/ai/anthropic/error.py +27 -0
- pyfect/ai/anthropic/language_model.py +366 -0
- pyfect/ai/anthropic/telemetry.py +51 -0
- pyfect/ai/anthropic/tool.py +570 -0
- pyfect/ai/anthropic_structured_output.py +138 -0
- pyfect/ai/chat.py +548 -0
- pyfect/ai/cloudflare/__init__.py +15 -0
- pyfect/ai/cloudflare/_errors.py +108 -0
- pyfect/ai/cloudflare/client.py +174 -0
- pyfect/ai/cloudflare/decision_model.py +117 -0
- pyfect/ai/cloudflare/schema.py +144 -0
- pyfect/ai/decision.py +290 -0
- pyfect/ai/decision_model.py +283 -0
- pyfect/ai/embedding_model.py +177 -0
- pyfect/ai/id_generator.py +79 -0
- pyfect/ai/language_model.py +1077 -0
- pyfect/ai/mcp_client.py +682 -0
- pyfect/ai/mcp_client_error.py +223 -0
- pyfect/ai/mcp_protocol.py +205 -0
- pyfect/ai/mcp_schema.py +1300 -0
- pyfect/ai/mcp_server.py +404 -0
- pyfect/ai/model.py +56 -0
- pyfect/ai/openai/__init__.py +49 -0
- pyfect/ai/openai/_errors.py +89 -0
- pyfect/ai/openai/_prompt.py +527 -0
- pyfect/ai/openai/_requests.py +34 -0
- pyfect/ai/openai/_response.py +569 -0
- pyfect/ai/openai/_schema.py +769 -0
- pyfect/ai/openai/_stream.py +771 -0
- pyfect/ai/openai/_tools.py +128 -0
- pyfect/ai/openai/_web_socket.py +295 -0
- pyfect/ai/openai/client.py +296 -0
- pyfect/ai/openai/client_generated.py +123 -0
- pyfect/ai/openai/config.py +34 -0
- pyfect/ai/openai/decision_model.py +177 -0
- pyfect/ai/openai/embedding_model.py +116 -0
- pyfect/ai/openai/error.py +8 -0
- pyfect/ai/openai/generated/__init__.py +7213 -0
- pyfect/ai/openai/generated/_recursive.py +18 -0
- pyfect/ai/openai/generated/_schemas_0.py +1015 -0
- pyfect/ai/openai/generated/_schemas_1.py +1016 -0
- pyfect/ai/openai/generated/_schemas_2.py +1017 -0
- pyfect/ai/openai/generated/_schemas_3.py +615 -0
- pyfect/ai/openai/generated/client.py +5298 -0
- pyfect/ai/openai/generated/types.py +8123 -0
- pyfect/ai/openai/language_model.py +304 -0
- pyfect/ai/openai/telemetry.py +19 -0
- pyfect/ai/openai/tool.py +391 -0
- pyfect/ai/openai_compat/__init__.py +25 -0
- pyfect/ai/openai_compat/_prompt.py +191 -0
- pyfect/ai/openai_compat/_request.py +153 -0
- pyfect/ai/openai_compat/_response.py +407 -0
- pyfect/ai/openai_compat/_schema.py +97 -0
- pyfect/ai/openai_compat/client.py +234 -0
- pyfect/ai/openai_compat/config.py +32 -0
- pyfect/ai/openai_compat/embedding_model.py +139 -0
- pyfect/ai/openai_compat/error.py +7 -0
- pyfect/ai/openai_compat/language_model.py +245 -0
- pyfect/ai/openai_compat/telemetry.py +51 -0
- pyfect/ai/openai_structured_output.py +118 -0
- pyfect/ai/openrouter/__init__.py +36 -0
- pyfect/ai/openrouter/_errors.py +186 -0
- pyfect/ai/openrouter/_prompt.py +272 -0
- pyfect/ai/openrouter/_response.py +226 -0
- pyfect/ai/openrouter/_stream.py +320 -0
- pyfect/ai/openrouter/client.py +283 -0
- pyfect/ai/openrouter/config.py +36 -0
- pyfect/ai/openrouter/decision_model.py +161 -0
- pyfect/ai/openrouter/error.py +23 -0
- pyfect/ai/openrouter/generated/__init__.py +3029 -0
- pyfect/ai/openrouter/generated/_recursive.py +15 -0
- pyfect/ai/openrouter/generated/_schemas_0.py +1015 -0
- pyfect/ai/openrouter/generated/_schemas_1.py +522 -0
- pyfect/ai/openrouter/generated/client.py +1445 -0
- pyfect/ai/openrouter/generated/types.py +4769 -0
- pyfect/ai/openrouter/language_model.py +345 -0
- pyfect/ai/openrouter/schema.py +165 -0
- pyfect/ai/prompt.py +664 -0
- pyfect/ai/response.py +768 -0
- pyfect/ai/response_id_tracker.py +82 -0
- pyfect/ai/telemetry.py +126 -0
- pyfect/ai/tokenizer.py +61 -0
- pyfect/ai/tool.py +536 -0
- pyfect/ai/toolkit.py +490 -0
- pyfect/ai/typesafe/__init__.py +17 -0
- pyfect/ai/typesafe/_errors.py +120 -0
- pyfect/ai/typesafe/client.py +146 -0
- pyfect/ai/typesafe/config.py +34 -0
- pyfect/ai/typesafe/decision_model.py +90 -0
- pyfect/ai/typesafe/schema.py +140 -0
- pyfect/batching/__init__.py +58 -0
- pyfect/batching/_api.py +71 -0
- pyfect/batching/_engine.py +229 -0
- pyfect/batching/_from_effect.py +76 -0
- pyfect/batching/_persisted.py +98 -0
- pyfect/batching/_request.py +133 -0
- pyfect/batching/_resolver.py +477 -0
- pyfect/batching/_with_cache.py +90 -0
- pyfect/big_decimal.py +500 -0
- pyfect/brand.py +114 -0
- pyfect/byte_size.py +354 -0
- pyfect/cache/__init__.py +29 -0
- pyfect/cache/_cache.py +426 -0
- pyfect/cache/_expiry.py +69 -0
- pyfect/cache/_scoped_cache.py +546 -0
- pyfect/case.py +174 -0
- pyfect/cause.py +462 -0
- pyfect/channel/__init__.py +155 -0
- pyfect/channel/_base.py +92 -0
- pyfect/channel/_catch.py +158 -0
- pyfect/channel/_catch_reason.py +572 -0
- pyfect/channel/_channel.py +158 -0
- pyfect/channel/_combinators.py +220 -0
- pyfect/channel/_concurrency_methods.py +217 -0
- pyfect/channel/_concurrent_map.py +118 -0
- pyfect/channel/_constructors.py +349 -0
- pyfect/channel/_context.py +232 -0
- pyfect/channel/_error_methods.py +336 -0
- pyfect/channel/_errors.py +180 -0
- pyfect/channel/_filters.py +225 -0
- pyfect/channel/_lifecycle.py +71 -0
- pyfect/channel/_lifecycle_methods.py +206 -0
- pyfect/channel/_loop.py +100 -0
- pyfect/channel/_merge.py +246 -0
- pyfect/channel/_repeat.py +154 -0
- pyfect/channel/_run.py +149 -0
- pyfect/channel/_run_methods.py +341 -0
- pyfect/channel/_sinks.py +88 -0
- pyfect/channel/_sources.py +238 -0
- pyfect/channel/_text.py +176 -0
- pyfect/channel/_transform_methods.py +415 -0
- pyfect/channel/_upstream.py +67 -0
- pyfect/channel_schema.py +131 -0
- pyfect/cli/__init__.py +79 -0
- pyfect/cli/_ansi.py +82 -0
- pyfect/cli/_command.py +469 -0
- pyfect/cli/_config.py +164 -0
- pyfect/cli/_environment.py +10 -0
- pyfect/cli/_help.py +195 -0
- pyfect/cli/_lexer.py +81 -0
- pyfect/cli/_parser.py +465 -0
- pyfect/cli/_settings.py +85 -0
- pyfect/cli/_suggest.py +33 -0
- pyfect/cli/_width.py +134 -0
- pyfect/cli/_wizard.py +317 -0
- pyfect/cli/argument.py +212 -0
- pyfect/cli/cli_config.py +40 -0
- pyfect/cli/cli_error.py +242 -0
- pyfect/cli/cli_output.py +305 -0
- pyfect/cli/command.py +440 -0
- pyfect/cli/completions/__init__.py +50 -0
- pyfect/cli/completions/_completer.ps1 +190 -0
- pyfect/cli/completions/_descriptor.py +182 -0
- pyfect/cli/completions/bash.py +300 -0
- pyfect/cli/completions/fish.py +164 -0
- pyfect/cli/completions/powershell.py +156 -0
- pyfect/cli/completions/zsh.py +189 -0
- pyfect/cli/flag.py +246 -0
- pyfect/cli/global_flag.py +99 -0
- pyfect/cli/help_doc.py +63 -0
- pyfect/cli/param.py +789 -0
- pyfect/cli/primitive.py +308 -0
- pyfect/cli/prompt/__init__.py +75 -0
- pyfect/cli/prompt/_auto_complete.py +205 -0
- pyfect/cli/prompt/_choices.py +104 -0
- pyfect/cli/prompt/_confirm.py +195 -0
- pyfect/cli/prompt/_core.py +512 -0
- pyfect/cli/prompt/_date.py +173 -0
- pyfect/cli/prompt/_date_parts.py +387 -0
- pyfect/cli/prompt/_file.py +372 -0
- pyfect/cli/prompt/_multi_select.py +204 -0
- pyfect/cli/prompt/_number.py +257 -0
- pyfect/cli/prompt/_select.py +117 -0
- pyfect/cli/prompt/_text.py +239 -0
- pyfect/cli/terminal_capabilities.py +118 -0
- pyfect/clock.py +150 -0
- pyfect/cluster/__init__.py +155 -0
- pyfect/cluster/_cluster_abandon.py +36 -0
- pyfect/cluster/_cluster_layer.py +122 -0
- pyfect/cluster/_entity_activation.py +16 -0
- pyfect/cluster/_entity_manager/__init__.py +22 -0
- pyfect/cluster/_entity_manager/_activation.py +540 -0
- pyfect/cluster/_entity_manager/_core.py +186 -0
- pyfect/cluster/_entity_manager/_decode.py +60 -0
- pyfect/cluster/_entity_manager/_manager.py +423 -0
- pyfect/cluster/_entity_manager/_state.py +113 -0
- pyfect/cluster/_entity_reaper.py +94 -0
- pyfect/cluster/_hash.py +13 -0
- pyfect/cluster/_interruptors.py +88 -0
- pyfect/cluster/_resource_map.py +179 -0
- pyfect/cluster/_resource_ref.py +266 -0
- pyfect/cluster/_shard_lock.py +14 -0
- pyfect/cluster/cluster_cron.py +230 -0
- pyfect/cluster/cluster_error.py +81 -0
- pyfect/cluster/cluster_http.py +210 -0
- pyfect/cluster/cluster_metrics.py +54 -0
- pyfect/cluster/cluster_schema.py +137 -0
- pyfect/cluster/cluster_socket.py +197 -0
- pyfect/cluster/cluster_workflow_engine/__init__.py +24 -0
- pyfect/cluster/cluster_workflow_engine/_engine.py +1099 -0
- pyfect/cluster/cluster_workflow_engine/_entities.py +298 -0
- pyfect/cluster/deliver_at.py +31 -0
- pyfect/cluster/entity/__init__.py +39 -0
- pyfect/cluster/entity/_entity.py +332 -0
- pyfect/cluster/entity/_handlers.py +168 -0
- pyfect/cluster/entity/_keep_alive.py +74 -0
- pyfect/cluster/entity/_queue.py +103 -0
- pyfect/cluster/entity/_request.py +34 -0
- pyfect/cluster/entity/_services.py +23 -0
- pyfect/cluster/entity/_test_client.py +237 -0
- pyfect/cluster/entity_address.py +25 -0
- pyfect/cluster/entity_id.py +20 -0
- pyfect/cluster/entity_proxy.py +161 -0
- pyfect/cluster/entity_proxy_server.py +103 -0
- pyfect/cluster/entity_resource.py +122 -0
- pyfect/cluster/entity_type.py +20 -0
- pyfect/cluster/envelope.py +307 -0
- pyfect/cluster/http_runner.py +254 -0
- pyfect/cluster/k8s_http_client.py +281 -0
- pyfect/cluster/k8s_types.py +1394 -0
- pyfect/cluster/machine_id.py +21 -0
- pyfect/cluster/message.py +219 -0
- pyfect/cluster/message_storage/__init__.py +67 -0
- pyfect/cluster/message_storage/_encoded.py +393 -0
- pyfect/cluster/message_storage/_memory.py +386 -0
- pyfect/cluster/message_storage/_noop.py +109 -0
- pyfect/cluster/message_storage/_service.py +388 -0
- pyfect/cluster/reply.py +236 -0
- pyfect/cluster/runner.py +32 -0
- pyfect/cluster/runner_address.py +23 -0
- pyfect/cluster/runner_health.py +93 -0
- pyfect/cluster/runner_server.py +286 -0
- pyfect/cluster/runner_storage.py +212 -0
- pyfect/cluster/runners/__init__.py +55 -0
- pyfect/cluster/runners/_local.py +460 -0
- pyfect/cluster/runners/_noop.py +57 -0
- pyfect/cluster/runners/_rpc.py +290 -0
- pyfect/cluster/runners/_service.py +113 -0
- pyfect/cluster/shard_id.py +39 -0
- pyfect/cluster/sharding/__init__.py +12 -0
- pyfect/cluster/sharding/_acquisition.py +490 -0
- pyfect/cluster/sharding/_clients.py +271 -0
- pyfect/cluster/sharding/_make.py +388 -0
- pyfect/cluster/sharding/_routing.py +296 -0
- pyfect/cluster/sharding/_runners_sync.py +198 -0
- pyfect/cluster/sharding/_service.py +91 -0
- pyfect/cluster/sharding/_singletons.py +183 -0
- pyfect/cluster/sharding/_state.py +296 -0
- pyfect/cluster/sharding/_storage_loop.py +421 -0
- pyfect/cluster/sharding_config.py +230 -0
- pyfect/cluster/sharding_registration_event.py +28 -0
- pyfect/cluster/single_runner.py +49 -0
- pyfect/cluster/singleton.py +36 -0
- pyfect/cluster/singleton_address.py +20 -0
- pyfect/cluster/snowflake.py +133 -0
- pyfect/cluster/socket_runner.py +67 -0
- pyfect/cluster/sql_message_storage.py +1001 -0
- pyfect/cluster/sql_runner_storage.py +841 -0
- pyfect/cluster/test_runner.py +23 -0
- pyfect/config/__init__.py +93 -0
- pyfect/config/_all.py +96 -0
- pyfect/config/_collections.py +291 -0
- pyfect/config/_config.py +261 -0
- pyfect/config/_constructors.py +106 -0
- pyfect/config/_error.py +27 -0
- pyfect/config/_leaf.py +92 -0
- pyfect/config/_scalars.py +193 -0
- pyfect/config/_schema.py +340 -0
- pyfect/config_provider/__init__.py +60 -0
- pyfect/config_provider/_dir.py +84 -0
- pyfect/config_provider/_dotenv.py +127 -0
- pyfect/config_provider/_env.py +79 -0
- pyfect/config_provider/_layers.py +80 -0
- pyfect/config_provider/_node.py +90 -0
- pyfect/config_provider/_provider.py +118 -0
- pyfect/config_provider/_unknown.py +62 -0
- pyfect/console/__init__.py +63 -0
- pyfect/console/_effects.py +134 -0
- pyfect/console/_live.py +156 -0
- pyfect/console/_service.py +49 -0
- pyfect/console/_table.py +90 -0
- pyfect/context.py +414 -0
- pyfect/cron/__init__.py +30 -0
- pyfect/cron/_model.py +183 -0
- pyfect/cron/_search.py +268 -0
- pyfect/cron/_text.py +202 -0
- pyfect/cron/_wall.py +42 -0
- pyfect/crypto.py +257 -0
- pyfect/date_time/__init__.py +77 -0
- pyfect/date_time/_calendar.py +209 -0
- pyfect/date_time/_current.py +118 -0
- pyfect/date_time/_epoch.py +22 -0
- pyfect/date_time/_iso.py +80 -0
- pyfect/date_time/_zones.py +135 -0
- pyfect/date_time/intl.py +144 -0
- pyfect/deferred.py +145 -0
- pyfect/devtools/__init__.py +24 -0
- pyfect/devtools/client.py +323 -0
- pyfect/devtools/layers.py +32 -0
- pyfect/devtools/schema.py +301 -0
- pyfect/devtools/server.py +72 -0
- pyfect/duration.py +295 -0
- pyfect/effect.py +565 -0
- pyfect/effectable.py +37 -0
- pyfect/encoding/__init__.py +62 -0
- pyfect/encoding/_archive.py +197 -0
- pyfect/encoding/_json_array_scanner.py +477 -0
- pyfect/encoding/base64.py +23 -0
- pyfect/encoding/base64_url.py +19 -0
- pyfect/encoding/encoding_error.py +17 -0
- pyfect/encoding/hex.py +28 -0
- pyfect/encoding/ini.py +188 -0
- pyfect/encoding/json_array.py +246 -0
- pyfect/encoding/ndjson.py +461 -0
- pyfect/encoding/schema_binary/__init__.py +257 -0
- pyfect/encoding/schema_binary/_channels.py +173 -0
- pyfect/encoding/schema_binary/_compile.py +584 -0
- pyfect/encoding/schema_binary/_decode.py +848 -0
- pyfect/encoding/schema_binary/_dictionary.py +90 -0
- pyfect/encoding/schema_binary/_encode.py +838 -0
- pyfect/encoding/schema_binary/_fingerprint.py +168 -0
- pyfect/encoding/schema_binary/_layout.py +350 -0
- pyfect/encoding/schema_binary/_literals.py +47 -0
- pyfect/encoding/schema_binary/_parser.py +163 -0
- pyfect/encoding/schema_binary/_reader.py +148 -0
- pyfect/encoding/schema_binary/_target.py +310 -0
- pyfect/encoding/schema_binary/_values.py +133 -0
- pyfect/encoding/schema_binary/_wire.py +253 -0
- pyfect/encoding/sse.py +476 -0
- pyfect/encoding/tar/__init__.py +396 -0
- pyfect/encoding/tar/_decoder.py +220 -0
- pyfect/encoding/tar/_encoder.py +83 -0
- pyfect/encoding/tar/_header.py +494 -0
- pyfect/encoding/tar/_model.py +82 -0
- pyfect/encoding/toml.py +375 -0
- pyfect/encoding/xml/__init__.py +222 -0
- pyfect/encoding/xml/_chars.py +119 -0
- pyfect/encoding/xml/_collector.py +106 -0
- pyfect/encoding/xml/_inputs.py +126 -0
- pyfect/encoding/xml/_model.py +114 -0
- pyfect/encoding/xml/_parser.py +694 -0
- pyfect/encoding/xml/_scanner.py +180 -0
- pyfect/encoding/xml/_writer.py +238 -0
- pyfect/encoding/yaml.py +739 -0
- pyfect/encoding/zip/__init__.py +392 -0
- pyfect/encoding/zip/_decoder.py +378 -0
- pyfect/encoding/zip/_format.py +122 -0
- pyfect/encoding/zip/_inflate.py +65 -0
- pyfect/encoding/zip/_model.py +86 -0
- pyfect/encoding/zip/_writer.py +311 -0
- pyfect/error_reporter.py +240 -0
- pyfect/errors.py +321 -0
- pyfect/eventlog/__init__.py +77 -0
- pyfect/eventlog/_conflicts.py +94 -0
- pyfect/eventlog/_crypto.py +64 -0
- pyfect/eventlog/_identity.py +26 -0
- pyfect/eventlog/_identity_derivation.py +79 -0
- pyfect/eventlog/_transferable_bytes.py +55 -0
- pyfect/eventlog/_uuid.py +33 -0
- pyfect/eventlog/event.py +115 -0
- pyfect/eventlog/event_group.py +55 -0
- pyfect/eventlog/event_journal.py +630 -0
- pyfect/eventlog/event_log.py +1039 -0
- pyfect/eventlog/event_log_encryption.py +219 -0
- pyfect/eventlog/event_log_message.py +357 -0
- pyfect/eventlog/event_log_remote.py +465 -0
- pyfect/eventlog/event_log_server.py +442 -0
- pyfect/eventlog/event_log_server_encrypted.py +322 -0
- pyfect/eventlog/event_log_server_unencrypted.py +814 -0
- pyfect/eventlog/event_log_session_auth.py +250 -0
- pyfect/eventlog/event_row.py +423 -0
- pyfect/eventlog/sql_event_journal.py +692 -0
- pyfect/eventlog/sql_event_log_server_encrypted.py +440 -0
- pyfect/eventlog/sql_event_log_server_unencrypted.py +568 -0
- pyfect/execution_plan/__init__.py +42 -0
- pyfect/execution_plan/_events.py +128 -0
- pyfect/execution_plan/_model.py +108 -0
- pyfect/execution_plan/_run.py +176 -0
- pyfect/exit.py +205 -0
- pyfect/fiber_handle.py +305 -0
- pyfect/fiber_map.py +350 -0
- pyfect/fiber_set.py +232 -0
- pyfect/file_system/__init__.py +69 -0
- pyfect/file_system/_copy.py +116 -0
- pyfect/file_system/_errors.py +32 -0
- pyfect/file_system/_file.py +177 -0
- pyfect/file_system/_live.py +351 -0
- pyfect/file_system/_model.py +72 -0
- pyfect/file_system/_noop.py +189 -0
- pyfect/file_system/_service.py +336 -0
- pyfect/file_system/_stat.py +69 -0
- pyfect/file_system/_watch.py +107 -0
- pyfect/filters.py +245 -0
- pyfect/graph/__init__.py +101 -0
- pyfect/graph/_all_pairs.py +150 -0
- pyfect/graph/_combine.py +259 -0
- pyfect/graph/_compact.py +143 -0
- pyfect/graph/_components.py +200 -0
- pyfect/graph/_cycles.py +144 -0
- pyfect/graph/_enumerate.py +168 -0
- pyfect/graph/_errors.py +51 -0
- pyfect/graph/_flow.py +164 -0
- pyfect/graph/_forest.py +109 -0
- pyfect/graph/_graph.py +269 -0
- pyfect/graph/_lowlink.py +98 -0
- pyfect/graph/_matching.py +101 -0
- pyfect/graph/_mutate.py +211 -0
- pyfect/graph/_query.py +138 -0
- pyfect/graph/_records.py +119 -0
- pyfect/graph/_render.py +109 -0
- pyfect/graph/_shortest.py +274 -0
- pyfect/graph/_snapshot.py +50 -0
- pyfect/graph/_store.py +136 -0
- pyfect/graph/_traverse.py +289 -0
- pyfect/graph/_view.py +384 -0
- pyfect/graph/_walker.py +70 -0
- pyfect/graphql/__init__.py +87 -0
- pyfect/graphql/_codec.py +117 -0
- pyfect/graphql/_http.py +309 -0
- pyfect/graphql/_schemas.py +39 -0
- pyfect/graphql/_slices.py +110 -0
- pyfect/graphql/_template.py +111 -0
- pyfect/graphql/_types.py +115 -0
- pyfect/graphql/_ws.py +395 -0
- pyfect/graphql/_ws_messages.py +120 -0
- pyfect/graphql/client.py +428 -0
- pyfect/graphql/client_error.py +285 -0
- pyfect/graphql/group.py +142 -0
- pyfect/graphql/middleware.py +98 -0
- pyfect/graphql/operation.py +409 -0
- pyfect/graphql/protocol.py +119 -0
- pyfect/graphql/resolver.py +574 -0
- pyfect/graphql/template.py +61 -0
- pyfect/hash_map.py +317 -0
- pyfect/hash_ring.py +206 -0
- pyfect/hash_set.py +163 -0
- pyfect/http/__init__.py +125 -0
- pyfect/http/_asgi_listener.py +272 -0
- pyfect/http/_authority.py +63 -0
- pyfect/http/_brotli.py +27 -0
- pyfect/http/_compression.py +52 -0
- pyfect/http/_field.py +18 -0
- pyfect/http/_foreign_path.py +27 -0
- pyfect/http/_form_data.py +34 -0
- pyfect/http/_httpx_options.py +120 -0
- pyfect/http/_in_memory.py +76 -0
- pyfect/http/_request_state.py +59 -0
- pyfect/http/_vary.py +14 -0
- pyfect/http/abort.py +65 -0
- pyfect/http/asgi/__init__.py +59 -0
- pyfect/http/asgi/_app.py +155 -0
- pyfect/http/asgi/_http.py +201 -0
- pyfect/http/asgi/_mount.py +263 -0
- pyfect/http/asgi/_scope.py +86 -0
- pyfect/http/asgi/_serve.py +72 -0
- pyfect/http/asgi/_types.py +13 -0
- pyfect/http/asgi/_web_socket.py +325 -0
- pyfect/http/body.py +437 -0
- pyfect/http/client/__init__.py +111 -0
- pyfect/http/client/_circuit_breaker.py +113 -0
- pyfect/http/client/_cookie_jar.py +120 -0
- pyfect/http/client/_make.py +252 -0
- pyfect/http/client/_rate_limit.py +423 -0
- pyfect/http/client/_redirects.py +93 -0
- pyfect/http/client/_retry.py +115 -0
- pyfect/http/client/_service.py +497 -0
- pyfect/http/cookies.py +444 -0
- pyfect/http/error.py +188 -0
- pyfect/http/etag.py +96 -0
- pyfect/http/file_like.py +49 -0
- pyfect/http/find_my_way/__init__.py +10 -0
- pyfect/http/find_my_way/_nodes.py +186 -0
- pyfect/http/find_my_way/_paths.py +43 -0
- pyfect/http/find_my_way/_query_string.py +67 -0
- pyfect/http/find_my_way/_router.py +401 -0
- pyfect/http/headers.py +343 -0
- pyfect/http/http_effect.py +198 -0
- pyfect/http/httpx_client.py +291 -0
- pyfect/http/hypercorn_server.py +122 -0
- pyfect/http/incoming_message.py +140 -0
- pyfect/http/live_server/__init__.py +191 -0
- pyfect/http/live_server/_body.py +164 -0
- pyfect/http/live_server/_exchange.py +321 -0
- pyfect/http/live_server/_head.py +194 -0
- pyfect/http/live_server/_inbound.py +156 -0
- pyfect/http/live_server/_listener.py +211 -0
- pyfect/http/live_server/_serve.py +225 -0
- pyfect/http/live_server/_timers.py +20 -0
- pyfect/http/live_server/_upgrade.py +108 -0
- pyfect/http/live_server/_websocket.py +286 -0
- pyfect/http/live_server/_ws_frames.py +242 -0
- pyfect/http/method.py +30 -0
- pyfect/http/middleware/__init__.py +61 -0
- pyfect/http/middleware/_compression.py +156 -0
- pyfect/http/middleware/_cors.py +129 -0
- pyfect/http/middleware/_decompression.py +274 -0
- pyfect/http/middleware/_forwarded.py +113 -0
- pyfect/http/middleware/_logger.py +73 -0
- pyfect/http/middleware/_search_params.py +34 -0
- pyfect/http/middleware/_tracer.py +153 -0
- pyfect/http/middleware/_types.py +16 -0
- pyfect/http/mime/__init__.py +69 -0
- pyfect/http/mime/_types.py +361 -0
- pyfect/http/multipart.py +613 -0
- pyfect/http/multipart_parser/__init__.py +348 -0
- pyfect/http/multipart_parser/_content_type.py +109 -0
- pyfect/http/multipart_parser/_headers.py +206 -0
- pyfect/http/multipart_parser/_search.py +85 -0
- pyfect/http/origin.py +146 -0
- pyfect/http/platform.py +593 -0
- pyfect/http/prometheus.py +34 -0
- pyfect/http/request.py +384 -0
- pyfect/http/response.py +474 -0
- pyfect/http/router/__init__.py +82 -0
- pyfect/http/router/_api.py +462 -0
- pyfect/http/router/_in_memory.py +76 -0
- pyfect/http/router/_markers.py +47 -0
- pyfect/http/router/_middleware.py +181 -0
- pyfect/http/router/_middleware_stack.py +51 -0
- pyfect/http/router/_provided.py +31 -0
- pyfect/http/router/_route.py +114 -0
- pyfect/http/router/_router.py +308 -0
- pyfect/http/schema.py +276 -0
- pyfect/http/server.py +199 -0
- pyfect/http/server_error.py +261 -0
- pyfect/http/server_request.py +729 -0
- pyfect/http/server_respondable.py +77 -0
- pyfect/http/server_response.py +569 -0
- pyfect/http/static_server/__init__.py +257 -0
- pyfect/http/static_server/_conditional.py +72 -0
- pyfect/http/static_server/_mime.py +55 -0
- pyfect/http/static_server/_range.py +53 -0
- pyfect/http/status.py +51 -0
- pyfect/http/template.py +161 -0
- pyfect/http/trace_context.py +91 -0
- pyfect/http/url.py +442 -0
- pyfect/http/url_params.py +304 -0
- pyfect/http/uvicorn_server.py +119 -0
- pyfect/http/web.py +171 -0
- pyfect/http/web_handler.py +463 -0
- pyfect/http/wire.py +209 -0
- pyfect/http_api/__init__.py +83 -0
- pyfect/http_api/_assets/scalar.js +8 -0
- pyfect/http_api/_assets/swagger-ui.css +3 -0
- pyfect/http_api/_assets/swagger-ui.js +4 -0
- pyfect/http_api/_client_method.py +220 -0
- pyfect/http_api/_client_payload.py +144 -0
- pyfect/http_api/_client_response.py +377 -0
- pyfect/http_api/_handlers.py +282 -0
- pyfect/http_api/_html.py +38 -0
- pyfect/http_api/_media_type.py +6 -0
- pyfect/http_api/_openapi_responses.py +164 -0
- pyfect/http_api/_openapi_schemas.py +163 -0
- pyfect/http_api/_path.py +102 -0
- pyfect/http_api/_payload.py +260 -0
- pyfect/http_api/_response.py +363 -0
- pyfect/http_api/_security.py +174 -0
- pyfect/http_api/api.py +265 -0
- pyfect/http_api/builder.py +447 -0
- pyfect/http_api/client.py +474 -0
- pyfect/http_api/endpoint.py +969 -0
- pyfect/http_api/error.py +280 -0
- pyfect/http_api/group.py +215 -0
- pyfect/http_api/middleware.py +232 -0
- pyfect/http_api/openapi.py +396 -0
- pyfect/http_api/scalar.py +143 -0
- pyfect/http_api/schema.py +713 -0
- pyfect/http_api/security.py +91 -0
- pyfect/http_api/swagger.py +69 -0
- pyfect/http_api/test.py +105 -0
- pyfect/json_patch.py +255 -0
- pyfect/json_pointer.py +100 -0
- pyfect/json_schema/__init__.py +66 -0
- pyfect/json_schema/_api.py +96 -0
- pyfect/json_schema/_converter.py +258 -0
- pyfect/json_schema/_draft04.py +198 -0
- pyfect/json_schema/_draft07.py +203 -0
- pyfect/json_schema/_keywords.py +202 -0
- pyfect/json_schema/_model.py +201 -0
- pyfect/json_schema/_openapi.py +208 -0
- pyfect/latch.py +127 -0
- pyfect/layer.py +479 -0
- pyfect/layer_map.py +279 -0
- pyfect/layer_ref.py +208 -0
- pyfect/logger/__init__.py +91 -0
- pyfect/logger/_batched.py +49 -0
- pyfect/logger/_console.py +116 -0
- pyfect/logger/_file.py +31 -0
- pyfect/logger/_formats.py +104 -0
- pyfect/logger/_layer.py +50 -0
- pyfect/logger/_level.py +53 -0
- pyfect/logger/_log.py +142 -0
- pyfect/logger/_model.py +163 -0
- pyfect/logger/_refs.py +78 -0
- pyfect/logger/_stdlib.py +54 -0
- pyfect/logger/_text.py +263 -0
- pyfect/logger/_tracer.py +35 -0
- pyfect/managed_runtime.py +393 -0
- pyfect/metric/__init__.py +119 -0
- pyfect/metric/_boundaries.py +27 -0
- pyfect/metric/_kinds.py +417 -0
- pyfect/metric/_metric.py +302 -0
- pyfect/metric/_prometheus.py +150 -0
- pyfect/metric/_refs.py +78 -0
- pyfect/metric/_runtime.py +75 -0
- pyfect/metric/_snapshot.py +97 -0
- pyfect/metric/_state.py +74 -0
- pyfect/msgspec.py +98 -0
- pyfect/mutable_hash_map.py +140 -0
- pyfect/mutable_hash_set.py +95 -0
- pyfect/net/__init__.py +18 -0
- pyfect/net/_errors.py +29 -0
- pyfect/net/address_resolver.py +179 -0
- pyfect/net/dns/__init__.py +92 -0
- pyfect/net/dns/_error.py +43 -0
- pyfect/net/dns/_records.py +335 -0
- pyfect/net/dns/_service.py +287 -0
- pyfect/net/dns/_system.py +89 -0
- pyfect/net/host.py +311 -0
- pyfect/net/ip_interface.py +202 -0
- pyfect/net/ip_network.py +250 -0
- pyfect/net/net_address/__init__.py +245 -0
- pyfect/net/net_address/_inet.py +412 -0
- pyfect/net/net_address/_ip.py +348 -0
- pyfect/net/net_address/_mac.py +119 -0
- pyfect/optic/__init__.py +50 -0
- pyfect/optic/_builtins.py +90 -0
- pyfect/optic/_data.py +97 -0
- pyfect/optic/_optic.py +422 -0
- pyfect/optic/_steps.py +151 -0
- pyfect/option.py +362 -0
- pyfect/otel/__init__.py +22 -0
- pyfect/otel/_attributes.py +67 -0
- pyfect/otel/_producer.py +322 -0
- pyfect/otel/_shutdown.py +26 -0
- pyfect/otel/_span.py +292 -0
- pyfect/otel/_wrapper.py +92 -0
- pyfect/otel/logger.py +154 -0
- pyfect/otel/metrics.py +126 -0
- pyfect/otel/resource.py +111 -0
- pyfect/otel/sdk.py +195 -0
- pyfect/otel/tracer.py +157 -0
- pyfect/partitioned_semaphore.py +211 -0
- pyfect/path.py +304 -0
- pyfect/persistence/__init__.py +77 -0
- pyfect/persistence/_columns.py +32 -0
- pyfect/persistence/_error.py +14 -0
- pyfect/persistence/_seconds.py +68 -0
- pyfect/persistence/_sql_keys.py +47 -0
- pyfect/persistence/circuit_breaker/__init__.py +120 -0
- pyfect/persistence/circuit_breaker/_breaker.py +317 -0
- pyfect/persistence/circuit_breaker/_errors.py +67 -0
- pyfect/persistence/circuit_breaker/_memory.py +243 -0
- pyfect/persistence/circuit_breaker/_redis.py +509 -0
- pyfect/persistence/circuit_breaker/_store.py +60 -0
- pyfect/persistence/circuit_breaker/_types.py +191 -0
- pyfect/persistence/key_value_store/__init__.py +51 -0
- pyfect/persistence/key_value_store/_layers.py +216 -0
- pyfect/persistence/key_value_store/_schema_store.py +91 -0
- pyfect/persistence/key_value_store/_sql.py +197 -0
- pyfect/persistence/key_value_store/_store.py +212 -0
- pyfect/persistence/persistable.py +164 -0
- pyfect/persistence/persisted_cache.py +159 -0
- pyfect/persistence/persisted_queue/__init__.py +75 -0
- pyfect/persistence/persisted_queue/_error.py +42 -0
- pyfect/persistence/persisted_queue/_mailbox.py +101 -0
- pyfect/persistence/persisted_queue/_memory.py +182 -0
- pyfect/persistence/persisted_queue/_queue.py +272 -0
- pyfect/persistence/persisted_queue/_redis.py +455 -0
- pyfect/persistence/persisted_queue/_redis_scripts.py +395 -0
- pyfect/persistence/persisted_queue/_sql.py +334 -0
- pyfect/persistence/persisted_queue/_sql_migrations.py +319 -0
- pyfect/persistence/persisted_queue/_sql_statements.py +441 -0
- pyfect/persistence/persisted_queue/_store.py +147 -0
- pyfect/persistence/persistence/__init__.py +84 -0
- pyfect/persistence/persistence/_kvs.py +135 -0
- pyfect/persistence/persistence/_memory.py +62 -0
- pyfect/persistence/persistence/_redis.py +162 -0
- pyfect/persistence/persistence/_service.py +276 -0
- pyfect/persistence/persistence/_sql.py +499 -0
- pyfect/persistence/rate_limiter/__init__.py +82 -0
- pyfect/persistence/rate_limiter/_errors.py +59 -0
- pyfect/persistence/rate_limiter/_limiter.py +325 -0
- pyfect/persistence/rate_limiter/_memory.py +217 -0
- pyfect/persistence/rate_limiter/_redis.py +485 -0
- pyfect/persistence/rate_limiter/_store.py +89 -0
- pyfect/persistence/redis/__init__.py +52 -0
- pyfect/persistence/redis/_error.py +12 -0
- pyfect/persistence/redis/_make.py +234 -0
- pyfect/persistence/redis/_script.py +78 -0
- pyfect/persistence/redis/_service.py +104 -0
- pyfect/persistence/redis_py.py +303 -0
- pyfect/persistence/user_directories.py +195 -0
- pyfect/platform.py +26 -0
- pyfect/platform_error.py +256 -0
- pyfect/pool/__init__.py +36 -0
- pyfect/pool/_pool.py +707 -0
- pyfect/pool/_state.py +161 -0
- pyfect/pool/_ttl.py +105 -0
- pyfect/pool/_waiters.py +116 -0
- pyfect/primary_key.py +35 -0
- pyfect/process/__init__.py +82 -0
- pyfect/process/_child.py +213 -0
- pyfect/process/_command.py +358 -0
- pyfect/process/_handle.py +77 -0
- pyfect/process/_live.py +455 -0
- pyfect/process/_os.py +78 -0
- pyfect/process/_pipes.py +291 -0
- pyfect/process/_spawner.py +75 -0
- pyfect/pubsub/__init__.py +33 -0
- pyfect/pubsub/_atomic.py +181 -0
- pyfect/pubsub/_hub.py +214 -0
- pyfect/pubsub/_pubsub.py +214 -0
- pyfect/pubsub/_replay.py +107 -0
- pyfect/pubsub/_subscription.py +226 -0
- pyfect/pull.py +132 -0
- pyfect/py.typed +0 -0
- pyfect/pydantic.py +188 -0
- pyfect/queue/__init__.py +33 -0
- pyfect/queue/_queue.py +525 -0
- pyfect/queue/_state.py +111 -0
- pyfect/queue/_views.py +55 -0
- pyfect/random.py +146 -0
- pyfect/rc_map.py +363 -0
- pyfect/rc_ref.py +271 -0
- pyfect/reactivity/__init__.py +53 -0
- pyfect/reactivity/_async_result_schema.py +227 -0
- pyfect/reactivity/_hydration.py +18 -0
- pyfect/reactivity/_live.py +21 -0
- pyfect/reactivity/_reactivity.py +256 -0
- pyfect/reactivity/_result_effect.py +17 -0
- pyfect/reactivity/_time.py +11 -0
- pyfect/reactivity/async_result.py +679 -0
- pyfect/reactivity/atom/__init__.py +143 -0
- pyfect/reactivity/atom/_combinators.py +223 -0
- pyfect/reactivity/atom/_constructors.py +80 -0
- pyfect/reactivity/atom/_conversions.py +79 -0
- pyfect/reactivity/atom/_effect.py +252 -0
- pyfect/reactivity/atom/_fn.py +341 -0
- pyfect/reactivity/atom/_kvs.py +101 -0
- pyfect/reactivity/atom/_optimistic.py +162 -0
- pyfect/reactivity/atom/_runtime.py +323 -0
- pyfect/reactivity/atom/_serializable.py +65 -0
- pyfect/reactivity/atom/_subscription_ref.py +102 -0
- pyfect/reactivity/atom/_swr.py +69 -0
- pyfect/reactivity/atom/_types.py +435 -0
- pyfect/reactivity/atom_http_api.py +667 -0
- pyfect/reactivity/atom_ref.py +260 -0
- pyfect/reactivity/atom_registry/__init__.py +219 -0
- pyfect/reactivity/atom_registry/_batch.py +96 -0
- pyfect/reactivity/atom_registry/_dispatcher.py +60 -0
- pyfect/reactivity/atom_registry/_lifetime.py +202 -0
- pyfect/reactivity/atom_registry/_node.py +423 -0
- pyfect/reactivity/atom_registry/_registry.py +380 -0
- pyfect/reactivity/atom_rpc.py +283 -0
- pyfect/reactivity/hydration.py +200 -0
- pyfect/redacted.py +83 -0
- pyfect/ref.py +129 -0
- pyfect/resource.py +98 -0
- pyfect/result.py +306 -0
- pyfect/rpc/__init__.py +64 -0
- pyfect/rpc/_encoded_schema.py +86 -0
- pyfect/rpc/_json_rpc.py +246 -0
- pyfect/rpc/_methods.py +177 -0
- pyfect/rpc/_rpc.py +498 -0
- pyfect/rpc/client/__init__.py +100 -0
- pyfect/rpc/client/_core.py +449 -0
- pyfect/rpc/client/_http.py +257 -0
- pyfect/rpc/client/_protocol.py +171 -0
- pyfect/rpc/client/_serialized.py +321 -0
- pyfect/rpc/client/_transports.py +328 -0
- pyfect/rpc/client/_worker.py +283 -0
- pyfect/rpc/client_error.py +35 -0
- pyfect/rpc/group.py +296 -0
- pyfect/rpc/message.py +299 -0
- pyfect/rpc/middleware.py +182 -0
- pyfect/rpc/schema.py +89 -0
- pyfect/rpc/serialization.py +366 -0
- pyfect/rpc/server/__init__.py +181 -0
- pyfect/rpc/server/_core.py +549 -0
- pyfect/rpc/server/_protocol.py +123 -0
- pyfect/rpc/server/_serialized.py +363 -0
- pyfect/rpc/server/_stdio.py +101 -0
- pyfect/rpc/server/_transports.py +427 -0
- pyfect/rpc/server/_worker.py +108 -0
- pyfect/rpc/test.py +74 -0
- pyfect/rpc/worker.py +92 -0
- pyfect/run.py +393 -0
- pyfect/schedule/__init__.py +91 -0
- pyfect/schedule/_combinators.py +168 -0
- pyfect/schedule/_constructors.py +223 -0
- pyfect/schedule/_cron.py +49 -0
- pyfect/schedule/_drive.py +191 -0
- pyfect/schedule/_model.py +405 -0
- pyfect/scheduler.py +575 -0
- pyfect/schema/__init__.py +866 -0
- pyfect/schema/_adapters.py +39 -0
- pyfect/schema/_arbitrary_constraint.py +242 -0
- pyfect/schema/_arbitrary_graph.py +325 -0
- pyfect/schema/_arbitrary_links.py +179 -0
- pyfect/schema/_arbitrary_number.py +112 -0
- pyfect/schema/_arrays.py +287 -0
- pyfect/schema/_assignable.py +118 -0
- pyfect/schema/_boolean_literals.py +31 -0
- pyfect/schema/_checks_number.py +216 -0
- pyfect/schema/_checks_order.py +140 -0
- pyfect/schema/_checks_size.py +277 -0
- pyfect/schema/_checks_string.py +241 -0
- pyfect/schema/_class.py +569 -0
- pyfect/schema/_codec_derivation.py +227 -0
- pyfect/schema/_codec_links.py +221 -0
- pyfect/schema/_codecs.py +316 -0
- pyfect/schema/_collections.py +172 -0
- pyfect/schema/_constructor_default.py +89 -0
- pyfect/schema/_data.py +440 -0
- pyfect/schema/_data_codecs.py +145 -0
- pyfect/schema/_decimal_regexp.py +243 -0
- pyfect/schema/_declare.py +68 -0
- pyfect/schema/_derive.py +292 -0
- pyfect/schema/_duration_codecs.py +27 -0
- pyfect/schema/_ecma_pattern.py +416 -0
- pyfect/schema/_error.py +26 -0
- pyfect/schema/_error_instance.py +222 -0
- pyfect/schema/_graph.py +271 -0
- pyfect/schema/_instances.py +240 -0
- pyfect/schema/_jsonschema/__init__.py +90 -0
- pyfect/schema/_jsonschema/_annotations.py +114 -0
- pyfect/schema/_jsonschema/_checks.py +253 -0
- pyfect/schema/_jsonschema/_compile.py +346 -0
- pyfect/schema/_jsonschema/_exact.py +19 -0
- pyfect/schema/_jsonschema/_merge.py +211 -0
- pyfect/schema/_jsonschema/_objects.py +231 -0
- pyfect/schema/_jsonschema/_references.py +221 -0
- pyfect/schema/_jsonschema/_template.py +69 -0
- pyfect/schema/_literals.py +147 -0
- pyfect/schema/_middleware.py +209 -0
- pyfect/schema/_net.py +522 -0
- pyfect/schema/_options.py +49 -0
- pyfect/schema/_parse.py +351 -0
- pyfect/schema/_parsed_text.py +37 -0
- pyfect/schema/_primitives.py +48 -0
- pyfect/schema/_record.py +41 -0
- pyfect/schema/_refine.py +81 -0
- pyfect/schema/_schema.py +158 -0
- pyfect/schema/_semver.py +144 -0
- pyfect/schema/_strategy/__init__.py +258 -0
- pyfect/schema/_strategy/_big_decimal.py +203 -0
- pyfect/schema/_strategy/_composites.py +119 -0
- pyfect/schema/_strategy/_leaves.py +94 -0
- pyfect/schema/_strategy/_net.py +133 -0
- pyfect/schema/_struct.py +87 -0
- pyfect/schema/_struct_keys.py +128 -0
- pyfect/schema/_tagged.py +254 -0
- pyfect/schema/_template_literal.py +172 -0
- pyfect/schema/_to_codec_array_from_single.py +115 -0
- pyfect/schema/_to_codec_go_json.py +174 -0
- pyfect/schema/_to_codec_json.py +73 -0
- pyfect/schema/_to_codec_string_tree.py +115 -0
- pyfect/schema/_to_differ_json_patch.py +44 -0
- pyfect/schema/_to_encoder_xml.py +119 -0
- pyfect/schema/_to_equivalence.py +186 -0
- pyfect/schema/_to_formatter.py +180 -0
- pyfect/schema/_to_iso.py +73 -0
- pyfect/schema/_to_strategy.py +31 -0
- pyfect/schema/_transform.py +210 -0
- pyfect/schema/_tree.py +27 -0
- pyfect/schema/_type_walk.py +53 -0
- pyfect/schema/_undefined.py +58 -0
- pyfect/schema/_value_codecs.py +84 -0
- pyfect/schema/_values.py +123 -0
- pyfect/schema/_void.py +86 -0
- pyfect/schema/ast/__init__.py +227 -0
- pyfect/schema/ast/_annotations.py +43 -0
- pyfect/schema/ast/_arrays.py +210 -0
- pyfect/schema/ast/_base.py +295 -0
- pyfect/schema/ast/_candidates.py +309 -0
- pyfect/schema/ast/_check.py +181 -0
- pyfect/schema/ast/_class.py +159 -0
- pyfect/schema/ast/_constructors.py +65 -0
- pyfect/schema/ast/_declaration.py +97 -0
- pyfect/schema/ast/_encoding.py +111 -0
- pyfect/schema/ast/_json.py +129 -0
- pyfect/schema/ast/_keywords.py +125 -0
- pyfect/schema/ast/_literal.py +157 -0
- pyfect/schema/ast/_objects.py +356 -0
- pyfect/schema/ast/_record.py +36 -0
- pyfect/schema/ast/_result.py +116 -0
- pyfect/schema/ast/_steps.py +209 -0
- pyfect/schema/ast/_suspend.py +65 -0
- pyfect/schema/ast/_template_literal.py +256 -0
- pyfect/schema/ast/_union.py +172 -0
- pyfect/schema/getter/__init__.py +136 -0
- pyfect/schema/getter/_binary.py +104 -0
- pyfect/schema/getter/_coerce.py +196 -0
- pyfect/schema/getter/_getter.py +243 -0
- pyfect/schema/getter/_make.py +151 -0
- pyfect/schema/getter/_text.py +141 -0
- pyfect/schema/getter/_tree_record.py +170 -0
- pyfect/schema/issue.py +420 -0
- pyfect/schema/model/__init__.py +120 -0
- pyfect/schema/model/_class.py +245 -0
- pyfect/schema/model/_dates.py +103 -0
- pyfect/schema/model/_fields.py +77 -0
- pyfect/schema/model/_toolkit.py +114 -0
- pyfect/schema/model/_uuid.py +69 -0
- pyfect/schema/representation/__init__.py +424 -0
- pyfect/schema/representation/_builtin_revivers.py +331 -0
- pyfect/schema/representation/_code.py +106 -0
- pyfect/schema/representation/_code_builtins.py +281 -0
- pyfect/schema/representation/_code_document.py +563 -0
- pyfect/schema/representation/_code_sort.py +111 -0
- pyfect/schema/representation/_from.py +278 -0
- pyfect/schema/representation/_import/__init__.py +78 -0
- pyfect/schema/representation/_import/_finalize.py +121 -0
- pyfect/schema/representation/_import/_intersect.py +467 -0
- pyfect/schema/representation/_import/_js_order.py +66 -0
- pyfect/schema/representation/_import/_keywords.py +224 -0
- pyfect/schema/representation/_import/_nodes.py +199 -0
- pyfect/schema/representation/_import/_translate.py +455 -0
- pyfect/schema/representation/_json.py +520 -0
- pyfect/schema/representation/_json_schema.py +178 -0
- pyfect/schema/representation/_model.py +284 -0
- pyfect/schema/representation/_revivers.py +89 -0
- pyfect/schema/representation/_to.py +249 -0
- pyfect/schema/transformation/__init__.py +99 -0
- pyfect/schema/transformation/_builtins.py +314 -0
- pyfect/schema/transformation/_durations.py +150 -0
- pyfect/schema/transformation/_transformation.py +177 -0
- pyfect/schema/variant_schema/__init__.py +69 -0
- pyfect/schema/variant_schema/_class.py +188 -0
- pyfect/schema/variant_schema/_defaults.py +30 -0
- pyfect/schema/variant_schema/_field.py +32 -0
- pyfect/schema/variant_schema/_struct.py +130 -0
- pyfect/schema/variant_schema/_toolkit.py +160 -0
- pyfect/scope.py +374 -0
- pyfect/scoped_ref.py +124 -0
- pyfect/semaphore.py +276 -0
- pyfect/semver.py +403 -0
- pyfect/semver_range.py +420 -0
- pyfect/sink/__init__.py +131 -0
- pyfect/sink/_combinators.py +151 -0
- pyfect/sink/_constructors.py +187 -0
- pyfect/sink/_context.py +87 -0
- pyfect/sink/_folds.py +336 -0
- pyfect/sink/_for_each.py +87 -0
- pyfect/sink/_sink.py +265 -0
- pyfect/sink/_steps.py +34 -0
- pyfect/sink/_taking.py +183 -0
- pyfect/socket/__init__.py +146 -0
- pyfect/socket/_accepted.py +65 -0
- pyfect/socket/_connection.py +384 -0
- pyfect/socket/_errors.py +135 -0
- pyfect/socket/_loop.py +66 -0
- pyfect/socket/_net.py +281 -0
- pyfect/socket/_socket.py +245 -0
- pyfect/socket/_tls.py +88 -0
- pyfect/socket/_tls_session.py +117 -0
- pyfect/socket/_web_socket.py +555 -0
- pyfect/socket/datagram/__init__.py +110 -0
- pyfect/socket/datagram/_core.py +460 -0
- pyfect/socket/datagram/_errors.py +102 -0
- pyfect/socket/datagram/_live.py +675 -0
- pyfect/socket/datagram/_model.py +217 -0
- pyfect/socket/server.py +258 -0
- pyfect/socket/ws.py +484 -0
- pyfect/sql/__init__.py +60 -0
- pyfect/sql/_loop.py +134 -0
- pyfect/sql/clickhouse/__init__.py +74 -0
- pyfect/sql/clickhouse/_connection.py +237 -0
- pyfect/sql/clickhouse/_rows.py +87 -0
- pyfect/sql/clickhouse/client.py +322 -0
- pyfect/sql/clickhouse/config.py +241 -0
- pyfect/sql/clickhouse/driver.py +434 -0
- pyfect/sql/clickhouse/error.py +106 -0
- pyfect/sql/clickhouse/migrator.py +47 -0
- pyfect/sql/clickhouse/params.py +168 -0
- pyfect/sql/client/__init__.py +35 -0
- pyfect/sql/client/_client.py +309 -0
- pyfect/sql/client/_sqlite_acquirers.py +130 -0
- pyfect/sql/client/_transaction.py +410 -0
- pyfect/sql/connection.py +87 -0
- pyfect/sql/d1/__init__.py +48 -0
- pyfect/sql/d1/api.py +270 -0
- pyfect/sql/d1/client.py +344 -0
- pyfect/sql/d1/config.py +55 -0
- pyfect/sql/error.py +311 -0
- pyfect/sql/libsql/__init__.py +58 -0
- pyfect/sql/libsql/_connection.py +110 -0
- pyfect/sql/libsql/_hrana.py +258 -0
- pyfect/sql/libsql/_local.py +251 -0
- pyfect/sql/libsql/_targets.py +28 -0
- pyfect/sql/libsql/_values.py +46 -0
- pyfect/sql/libsql/client.py +225 -0
- pyfect/sql/libsql/config.py +170 -0
- pyfect/sql/libsql/migrator.py +43 -0
- pyfect/sql/libsql/sdk.py +123 -0
- pyfect/sql/migrator.py +434 -0
- pyfect/sql/model.py +324 -0
- pyfect/sql/mssql/__init__.py +79 -0
- pyfect/sql/mssql/_connection.py +169 -0
- pyfect/sql/mssql/_driver.py +103 -0
- pyfect/sql/mssql/_names.py +9 -0
- pyfect/sql/mssql/_pool.py +125 -0
- pyfect/sql/mssql/_tds.py +447 -0
- pyfect/sql/mssql/_tds_params.py +155 -0
- pyfect/sql/mssql/_worker.py +155 -0
- pyfect/sql/mssql/client.py +344 -0
- pyfect/sql/mssql/config.py +118 -0
- pyfect/sql/mssql/error.py +111 -0
- pyfect/sql/mssql/migrator.py +43 -0
- pyfect/sql/mssql/parameter.py +44 -0
- pyfect/sql/mssql/procedure.py +119 -0
- pyfect/sql/mssql/types.py +137 -0
- pyfect/sql/mysql/__init__.py +60 -0
- pyfect/sql/mysql/_asyncmy.py +168 -0
- pyfect/sql/mysql/_bind.py +76 -0
- pyfect/sql/mysql/_connection.py +156 -0
- pyfect/sql/mysql/_pool.py +63 -0
- pyfect/sql/mysql/_session.py +262 -0
- pyfect/sql/mysql/client.py +244 -0
- pyfect/sql/mysql/config.py +108 -0
- pyfect/sql/mysql/error.py +104 -0
- pyfect/sql/mysql/migrator.py +44 -0
- pyfect/sql/pg/__init__.py +82 -0
- pyfect/sql/pg/_resolve.py +284 -0
- pyfect/sql/pg/_session.py +380 -0
- pyfect/sql/pg/client.py +398 -0
- pyfect/sql/pg/config.py +142 -0
- pyfect/sql/pg/connection.py +585 -0
- pyfect/sql/pg/error.py +131 -0
- pyfect/sql/pg/migrator.py +139 -0
- pyfect/sql/pg/pool.py +265 -0
- pyfect/sql/resolver.py +294 -0
- pyfect/sql/result.py +24 -0
- pyfect/sql/schema.py +116 -0
- pyfect/sql/sqlite/__init__.py +26 -0
- pyfect/sql/sqlite/_connection.py +230 -0
- pyfect/sql/sqlite/_worker.py +71 -0
- pyfect/sql/sqlite/client.py +248 -0
- pyfect/sql/sqlite/migrator.py +43 -0
- pyfect/sql/statement/__init__.py +144 -0
- pyfect/sql/statement/_compiler.py +384 -0
- pyfect/sql/statement/_segments.py +487 -0
- pyfect/sql/statement/_statement.py +526 -0
- pyfect/sql/statement/_transforms.py +93 -0
- pyfect/sql/stream.py +88 -0
- pyfect/stdio.py +206 -0
- pyfect/stream/__init__.py +151 -0
- pyfect/stream/_async_iterable.py +129 -0
- pyfect/stream/_base.py +69 -0
- pyfect/stream/_bridge_methods.py +139 -0
- pyfect/stream/_catch.py +135 -0
- pyfect/stream/_catch_reason.py +558 -0
- pyfect/stream/_channel_methods.py +61 -0
- pyfect/stream/_chunk_methods.py +169 -0
- pyfect/stream/_chunking.py +310 -0
- pyfect/stream/_concat.py +65 -0
- pyfect/stream/_constructors.py +341 -0
- pyfect/stream/_context.py +147 -0
- pyfect/stream/_error_methods.py +254 -0
- pyfect/stream/_execution_plan.py +199 -0
- pyfect/stream/_fanout.py +154 -0
- pyfect/stream/_fanout_methods.py +183 -0
- pyfect/stream/_filter_methods.py +203 -0
- pyfect/stream/_grouping.py +168 -0
- pyfect/stream/_many.py +65 -0
- pyfect/stream/_map_methods.py +233 -0
- pyfect/stream/_mapping.py +126 -0
- pyfect/stream/_run.py +114 -0
- pyfect/stream/_run_methods.py +114 -0
- pyfect/stream/_service_methods.py +36 -0
- pyfect/stream/_sink_methods.py +151 -0
- pyfect/stream/_sinking.py +277 -0
- pyfect/stream/_sources.py +124 -0
- pyfect/stream/_stream.py +162 -0
- pyfect/stream/_taking.py +330 -0
- pyfect/stream/_text.py +43 -0
- pyfect/stream/_timing.py +294 -0
- pyfect/stream/_timing_methods.py +252 -0
- pyfect/stream/_zip_methods.py +279 -0
- pyfect/stream/_zipping.py +213 -0
- pyfect/subscription_ref.py +66 -0
- pyfect/synchronized_ref.py +38 -0
- pyfect/take.py +36 -0
- pyfect/terminal/__init__.py +41 -0
- pyfect/terminal/_keys.py +113 -0
- pyfect/terminal/_live.py +223 -0
- pyfect/terminal/_service.py +73 -0
- pyfect/testing/__init__.py +122 -0
- pyfect/testing/_assertions.py +92 -0
- pyfect/testing/_clock.py +309 -0
- pyfect/testing/_console.py +164 -0
- pyfect/testing/_crypto.py +44 -0
- pyfect/testing/_decorators.py +211 -0
- pyfect/testing/_flaky.py +34 -0
- pyfect/testing/_layers.py +153 -0
- pyfect/testing/_prop.py +255 -0
- pyfect/testing/_runners.py +84 -0
- pyfect/testing/_runtime.py +174 -0
- pyfect/testing/_schema.py +238 -0
- pyfect/testing/plugin.py +59 -0
- pyfect/tracer/__init__.py +107 -0
- pyfect/tracer/_model.py +176 -0
- pyfect/tracer/_native.py +172 -0
- pyfect/tracer/_refs.py +87 -0
- pyfect/tracer/_spans.py +269 -0
- pyfect/transaction.py +340 -0
- pyfect/trie.py +317 -0
- pyfect/tx_chunk.py +127 -0
- pyfect/tx_deferred.py +75 -0
- pyfect/tx_hash_map.py +265 -0
- pyfect/tx_hash_set.py +151 -0
- pyfect/tx_priority_queue.py +224 -0
- pyfect/tx_pubsub.py +194 -0
- pyfect/tx_queue/__init__.py +27 -0
- pyfect/tx_queue/_queue.py +359 -0
- pyfect/tx_queue/_views.py +60 -0
- pyfect/tx_reentrant_lock.py +201 -0
- pyfect/tx_ref.py +79 -0
- pyfect/tx_semaphore.py +136 -0
- pyfect/tx_subscription_ref.py +118 -0
- pyfect/workers/__init__.py +62 -0
- pyfect/workers/_frames.py +82 -0
- pyfect/workers/_mailbox.py +51 -0
- pyfect/workers/_port.py +106 -0
- pyfect/workers/_process.py +155 -0
- pyfect/workers/_thread.py +100 -0
- pyfect/workers/python_worker.py +133 -0
- pyfect/workers/python_worker_runner.py +112 -0
- pyfect/workers/transferable.py +103 -0
- pyfect/workers/worker.py +273 -0
- pyfect/workers/worker_error.py +86 -0
- pyfect/workers/worker_runner.py +85 -0
- pyfect/workflow/__init__.py +78 -0
- pyfect/workflow/_crypto.py +23 -0
- pyfect/workflow/_encoded.py +381 -0
- pyfect/workflow/_memory.py +424 -0
- pyfect/workflow/_result.py +43 -0
- pyfect/workflow/_services.py +241 -0
- pyfect/workflow/_suspension.py +153 -0
- pyfect/workflow/activity.py +427 -0
- pyfect/workflow/durable_clock.py +72 -0
- pyfect/workflow/durable_deferred.py +403 -0
- pyfect/workflow/durable_queue.py +259 -0
- pyfect/workflow/workflow.py +509 -0
- pyfect/workflow/workflow_engine.py +42 -0
- pyfect/workflow/workflow_proxy.py +161 -0
- pyfect/workflow/workflow_proxy_server.py +146 -0
- pyfect_dev-0.1.0.dist-info/METADATA +296 -0
- pyfect_dev-0.1.0.dist-info/RECORD +1265 -0
- pyfect_dev-0.1.0.dist-info/WHEEL +4 -0
- pyfect_dev-0.1.0.dist-info/entry_points.txt +3 -0
- pyfect_dev-0.1.0.dist-info/licenses/LICENSE +21 -0
pyfect/graphql/_http.py
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""GraphQL over HTTP (Effect's ``GraphQLProtocol.makeHttp``): queries and
|
|
2
|
+
mutations POSTed as JSON (or queries as GET URL params), subscriptions over
|
|
3
|
+
graphql-sse in distinct connections mode."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from collections.abc import Callable, Mapping
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any, Literal, Never, cast
|
|
10
|
+
|
|
11
|
+
from pyfect import _json_text, stream
|
|
12
|
+
from pyfect import effect as fx
|
|
13
|
+
from pyfect.clock import ClockService, clock_with, now_millis_unsafe
|
|
14
|
+
from pyfect.context import Context
|
|
15
|
+
from pyfect.duration import Seconds, to_seconds
|
|
16
|
+
from pyfect.effect import Effect
|
|
17
|
+
from pyfect.encoding import sse
|
|
18
|
+
from pyfect.graphql._types import (
|
|
19
|
+
Answer,
|
|
20
|
+
BatchUrl,
|
|
21
|
+
ForcePost,
|
|
22
|
+
GraphQLProtocolService,
|
|
23
|
+
GraphQLRequest,
|
|
24
|
+
Kind,
|
|
25
|
+
ResolvedUrl,
|
|
26
|
+
request_body,
|
|
27
|
+
)
|
|
28
|
+
from pyfect.graphql.client_error import TransportError
|
|
29
|
+
from pyfect.http import error as http_error
|
|
30
|
+
from pyfect.http import request as http_request
|
|
31
|
+
from pyfect.http import url_params as _url_params
|
|
32
|
+
from pyfect.http.client import HttpClient
|
|
33
|
+
from pyfect.http.headers import Headers
|
|
34
|
+
from pyfect.http.request import HttpClientRequest
|
|
35
|
+
from pyfect.http.response import HttpClientResponse, filter_status_ok
|
|
36
|
+
from pyfect.stream import Stream
|
|
37
|
+
|
|
38
|
+
_GRAPHQL_RESPONSE = "application/graphql-response+json"
|
|
39
|
+
_JSON = "application/json"
|
|
40
|
+
_EVENT_STREAM = "text/event-stream"
|
|
41
|
+
_GET_PARAMS = frozenset({"query", "operationName", "variables", "extensions"})
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
45
|
+
class UrlOptions:
|
|
46
|
+
"""What a URL function gets: the caller's services and the operation."""
|
|
47
|
+
|
|
48
|
+
context: Context[Never]
|
|
49
|
+
kind: Kind
|
|
50
|
+
operation_name: str
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
type Url = str | Effect[str] | Callable[[UrlOptions], str]
|
|
54
|
+
"""An endpoint: fixed, resolved by an effect in each call's context, or
|
|
55
|
+
chosen per call from the caller's services and the operation."""
|
|
56
|
+
|
|
57
|
+
type QueryMethod = Literal["GET", "POST"]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def url_resolver(url: Url) -> Callable[[Kind, str], Effect[str]]:
|
|
61
|
+
"""``url`` as a function of the operation, run in the caller's context."""
|
|
62
|
+
if isinstance(url, str):
|
|
63
|
+
fixed: Effect[str] = fx.succeed(url)
|
|
64
|
+
return lambda _kind, _name: fixed
|
|
65
|
+
if isinstance(url, Effect):
|
|
66
|
+
resolved = cast("Effect[str]", url)
|
|
67
|
+
return lambda _kind, _name: resolved
|
|
68
|
+
choose = url
|
|
69
|
+
|
|
70
|
+
def chosen(kind: Kind, name: str) -> Effect[str]:
|
|
71
|
+
options: Effect[Context[Never]] = fx.context()
|
|
72
|
+
return options.map(
|
|
73
|
+
lambda context: choose(UrlOptions(context=context, kind=kind, operation_name=name))
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return chosen
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _is_object(value: object) -> bool:
|
|
80
|
+
return isinstance(value, Mapping)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _is_graphql_body(body: object) -> bool:
|
|
84
|
+
if not isinstance(body, Mapping):
|
|
85
|
+
return False
|
|
86
|
+
fields = cast("Mapping[str, object]", body)
|
|
87
|
+
if "data" not in fields and "errors" not in fields:
|
|
88
|
+
return False
|
|
89
|
+
if "errors" in fields and not isinstance(fields["errors"], list):
|
|
90
|
+
return False
|
|
91
|
+
return "data" not in fields or fields["data"] is None or _is_object(fields["data"])
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _post(url: str, request: GraphQLRequest, accept: str) -> HttpClientRequest:
|
|
95
|
+
return (
|
|
96
|
+
http_request.post(url)
|
|
97
|
+
.set_headers(request.headers)
|
|
98
|
+
.set_header("accept", accept)
|
|
99
|
+
.append_url_params(request.url_params)
|
|
100
|
+
.body_json_unsafe(request_body(request))
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _get(url: str, request: GraphQLRequest, accept: str) -> HttpClientRequest:
|
|
105
|
+
base, hash_mark, fragment = url.partition("#")
|
|
106
|
+
path, _, query = base.partition("?")
|
|
107
|
+
kept = [
|
|
108
|
+
(key, value)
|
|
109
|
+
for key, value in (*_url_params.parse(query).params, *request.url_params.params)
|
|
110
|
+
if key not in _GET_PARAMS
|
|
111
|
+
]
|
|
112
|
+
params: dict[str, str] = {"query": request.query, "operationName": request.operation_name}
|
|
113
|
+
if request.variables is not None:
|
|
114
|
+
params["variables"] = _json_text.js_stringify(request.variables)
|
|
115
|
+
if request.extensions is not None:
|
|
116
|
+
params["extensions"] = _json_text.js_stringify(request.extensions)
|
|
117
|
+
return (
|
|
118
|
+
http_request.get(path + hash_mark + fragment)
|
|
119
|
+
.set_headers(request.headers)
|
|
120
|
+
.set_header("accept", accept)
|
|
121
|
+
.append_url_params(kept)
|
|
122
|
+
.set_url_params(params)
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _content_type(response: HttpClientResponse) -> str:
|
|
127
|
+
return response.headers.get("content-type", "").lower()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _read_body(
|
|
131
|
+
response: HttpClientResponse,
|
|
132
|
+
) -> Effect[object, TransportError | http_error.HttpClientError]:
|
|
133
|
+
content_type = _content_type(response)
|
|
134
|
+
if _GRAPHQL_RESPONSE in content_type:
|
|
135
|
+
return response.json
|
|
136
|
+
if _JSON in content_type:
|
|
137
|
+
|
|
138
|
+
def checked(body: object) -> Effect[object, http_error.HttpClientError]:
|
|
139
|
+
if _is_graphql_body(body):
|
|
140
|
+
return fx.succeed(body)
|
|
141
|
+
return filter_status_ok(response).as_(body)
|
|
142
|
+
|
|
143
|
+
return response.json.flat_map(checked)
|
|
144
|
+
received = "no Content-Type" if content_type == "" else f"Content-Type {content_type}"
|
|
145
|
+
not_graphql = TransportError(
|
|
146
|
+
description=f"Expected a GraphQL response, received {received}",
|
|
147
|
+
status=response.status,
|
|
148
|
+
headers=response.headers,
|
|
149
|
+
)
|
|
150
|
+
return filter_status_ok(response).flat_map(lambda _: fx.fail(not_graphql))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _read_answer(
|
|
154
|
+
response: HttpClientResponse,
|
|
155
|
+
) -> Effect[Answer, TransportError | http_error.HttpClientError]:
|
|
156
|
+
return _read_body(response).map(
|
|
157
|
+
lambda body: Answer(body=body, status=response.status, headers=response.headers)
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _transport_error(clock: ClockService) -> Callable[[object], TransportError]:
|
|
162
|
+
def convert(error: object) -> TransportError:
|
|
163
|
+
if isinstance(error, http_error.HttpClientError):
|
|
164
|
+
return TransportError.from_http_client_error(error, now_millis=now_millis_unsafe(clock))
|
|
165
|
+
return cast("TransportError", error)
|
|
166
|
+
|
|
167
|
+
return convert
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _sse_answers(response: HttpClientResponse) -> Stream[Answer, TransportError]:
|
|
171
|
+
"""The ``next`` events of a graphql-sse body until ``complete``; a body
|
|
172
|
+
ending without one fails, with the last ``retry:`` as ``retry_after``."""
|
|
173
|
+
|
|
174
|
+
def build() -> Stream[Answer, TransportError]:
|
|
175
|
+
headers: Headers = response.headers
|
|
176
|
+
state: dict[str, Any] = {"retry_after": None, "complete": False}
|
|
177
|
+
pending: list[sse.Event] = []
|
|
178
|
+
|
|
179
|
+
def on_parse(event: sse.AnyEvent) -> None:
|
|
180
|
+
if isinstance(event, sse.Retry):
|
|
181
|
+
state["retry_after"] = to_seconds(event.duration)
|
|
182
|
+
else:
|
|
183
|
+
pending.append(event)
|
|
184
|
+
|
|
185
|
+
parser = sse.make_parser(on_parse, dispatch_empty_named_events=True)
|
|
186
|
+
|
|
187
|
+
def feed(text: str) -> Effect[list[sse.Event], TransportError]:
|
|
188
|
+
error = parser.feed(text)
|
|
189
|
+
if error is not None:
|
|
190
|
+
return fx.fail(TransportError(description=error.message, cause=error))
|
|
191
|
+
batch = list(pending)
|
|
192
|
+
pending.clear()
|
|
193
|
+
return fx.succeed(batch)
|
|
194
|
+
|
|
195
|
+
def until_complete(event: sse.Event) -> bool:
|
|
196
|
+
if event.event == "complete":
|
|
197
|
+
state["complete"] = True
|
|
198
|
+
return not state["complete"]
|
|
199
|
+
|
|
200
|
+
def answer(event: sse.Event) -> Effect[Answer, TransportError]:
|
|
201
|
+
try:
|
|
202
|
+
body: object = _json_text.parse(event.data)
|
|
203
|
+
except ValueError as cause:
|
|
204
|
+
return fx.fail(
|
|
205
|
+
TransportError(
|
|
206
|
+
description=f"Invalid graphql-sse next event: {cause}", cause=cause
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
return fx.succeed(Answer(body=body, headers=headers))
|
|
210
|
+
|
|
211
|
+
def stream_failed(cause: http_error.HttpClientError) -> TransportError:
|
|
212
|
+
retry_after: Seconds | None = state["retry_after"]
|
|
213
|
+
return TransportError(
|
|
214
|
+
description=f"The graphql-sse stream failed: {cause.message}",
|
|
215
|
+
retry_after=retry_after,
|
|
216
|
+
cause=cause,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def ended() -> Stream[Never, TransportError]:
|
|
220
|
+
if state["complete"]:
|
|
221
|
+
return stream.make()
|
|
222
|
+
retry_after: Seconds | None = state["retry_after"]
|
|
223
|
+
return stream.fail(
|
|
224
|
+
TransportError(
|
|
225
|
+
description="The graphql-sse stream ended without a complete event",
|
|
226
|
+
retry_after=retry_after,
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
events: Stream[sse.Event, TransportError] = (
|
|
231
|
+
response.stream.decode_text()
|
|
232
|
+
.map_error(stream_failed)
|
|
233
|
+
.map_effect(feed)
|
|
234
|
+
.flatten_iterable()
|
|
235
|
+
)
|
|
236
|
+
return (
|
|
237
|
+
events.take_while(until_complete)
|
|
238
|
+
.filter(lambda event: event.event == "next")
|
|
239
|
+
.map_effect(answer)
|
|
240
|
+
.concat(stream.suspend(ended))
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
return stream.suspend(build)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def make_http_sse(
|
|
247
|
+
*, url: Url, query_method: QueryMethod = "POST"
|
|
248
|
+
) -> Effect[GraphQLProtocolService, Never, HttpClient]:
|
|
249
|
+
"""A transport POSTing queries and mutations as JSON to ``url``
|
|
250
|
+
(queries as GET URL params with ``query_method="GET"``, unless a
|
|
251
|
+
resolver batch forces POST), and running subscriptions over
|
|
252
|
+
graphql-sse. A non-GraphQL response is a ``TransportError`` with its
|
|
253
|
+
status, headers and ``Retry-After``."""
|
|
254
|
+
resolve = url_resolver(url)
|
|
255
|
+
accept = f"{_GRAPHQL_RESPONSE}, {_JSON}"
|
|
256
|
+
|
|
257
|
+
def url_of(request: GraphQLRequest) -> Effect[str]:
|
|
258
|
+
# A resolver batch carries the URL it was grouped by.
|
|
259
|
+
def chosen(batch: BatchUrl | None) -> Effect[str]:
|
|
260
|
+
if batch is not None and batch.resolve is resolve:
|
|
261
|
+
return fx.succeed(batch.url)
|
|
262
|
+
return resolve(request.kind, request.operation_name)
|
|
263
|
+
|
|
264
|
+
return ResolvedUrl().flat_map(chosen)
|
|
265
|
+
|
|
266
|
+
def build(clock: ClockService) -> Effect[GraphQLProtocolService, Never, HttpClient]:
|
|
267
|
+
convert = _transport_error(clock)
|
|
268
|
+
|
|
269
|
+
def with_client(client: Any) -> GraphQLProtocolService:
|
|
270
|
+
def execute(request: GraphQLRequest) -> Effect[Answer, TransportError]:
|
|
271
|
+
def send(force_post: bool, endpoint: str) -> Effect[Answer, TransportError]:
|
|
272
|
+
by_get = query_method == "GET" and request.kind == "query" and not force_post
|
|
273
|
+
to_request = _get if by_get else _post
|
|
274
|
+
answer: Effect[Answer, Any] = client.execute(
|
|
275
|
+
to_request(endpoint, request, accept)
|
|
276
|
+
).flat_map(_read_answer)
|
|
277
|
+
return answer.map_error(convert)
|
|
278
|
+
|
|
279
|
+
return ForcePost().flat_map(
|
|
280
|
+
lambda force_post: url_of(request).flat_map(
|
|
281
|
+
lambda endpoint: send(force_post, endpoint)
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def subscribe(request: GraphQLRequest) -> Stream[Answer, TransportError]:
|
|
286
|
+
def opened(
|
|
287
|
+
response: HttpClientResponse,
|
|
288
|
+
) -> Effect[Stream[Answer, TransportError], Any]:
|
|
289
|
+
is_ok = 200 <= response.status < 300
|
|
290
|
+
if _EVENT_STREAM in _content_type(response) and is_ok:
|
|
291
|
+
return fx.succeed(_sse_answers(response))
|
|
292
|
+
return _read_answer(response).map(stream.succeed)
|
|
293
|
+
|
|
294
|
+
def send(endpoint: str) -> Effect[Stream[Answer, TransportError], TransportError]:
|
|
295
|
+
opening: Effect[Stream[Answer, TransportError], Any] = client.execute(
|
|
296
|
+
_post(endpoint, request, _EVENT_STREAM)
|
|
297
|
+
).flat_map(opened)
|
|
298
|
+
return opening.map_error(convert)
|
|
299
|
+
|
|
300
|
+
answers: Stream[Stream[Answer, TransportError], TransportError] = (
|
|
301
|
+
stream.from_effect(url_of(request).flat_map(send))
|
|
302
|
+
)
|
|
303
|
+
return answers.flat_map(lambda inner: inner)
|
|
304
|
+
|
|
305
|
+
return GraphQLProtocolService(execute=execute, subscribe=subscribe, url=resolve)
|
|
306
|
+
|
|
307
|
+
return HttpClient().map(with_client)
|
|
308
|
+
|
|
309
|
+
return clock_with(build)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Schemas the GraphQL errors and results share: ``HEADERS`` (a string
|
|
2
|
+
record decoded into ``Headers``) and ``SECONDS_FROM_MILLIS`` (Effect's
|
|
3
|
+
``Schema.DurationFromMillis`` over ``Seconds``)."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TypeIs
|
|
8
|
+
|
|
9
|
+
from pyfect import schema as S
|
|
10
|
+
from pyfect.duration import Seconds, to_millis
|
|
11
|
+
from pyfect.http.headers import Headers, from_input
|
|
12
|
+
from pyfect.schema import transformation as T
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _is_seconds(value: object) -> TypeIs[Seconds]:
|
|
16
|
+
return isinstance(value, Seconds)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _is_headers(value: object) -> TypeIs[Headers]:
|
|
20
|
+
return isinstance(value, Headers)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _plain(headers: Headers) -> dict[str, str]:
|
|
24
|
+
return dict(headers)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
SECONDS_FROM_MILLIS = S.Finite.pipe(
|
|
28
|
+
S.decode_to(
|
|
29
|
+
S.declare(_is_seconds, expected="Seconds"),
|
|
30
|
+
T.transform(decode=lambda millis: Seconds(millis / 1000), encode=to_millis),
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
HEADERS = S.Record(S.String, S.String).pipe(
|
|
35
|
+
S.decode_to(
|
|
36
|
+
S.declare(_is_headers, expected="Headers"),
|
|
37
|
+
T.transform(decode=from_input, encode=_plain),
|
|
38
|
+
)
|
|
39
|
+
)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Reading a batched response back into one result per slice, and the
|
|
2
|
+
helpers resolvers use to compare keys and walk results."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections.abc import Hashable, Mapping, Sequence
|
|
7
|
+
from dataclasses import dataclass, replace
|
|
8
|
+
from typing import Any, cast
|
|
9
|
+
|
|
10
|
+
from pyfect import _json_text
|
|
11
|
+
from pyfect.graphql._types import ExecutionResult
|
|
12
|
+
from pyfect.graphql.client_error import GraphQLError
|
|
13
|
+
from pyfect.graphql.template import DocumentTemplate
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def canonical_json(value: object) -> str:
|
|
17
|
+
"""JSON with object keys sorted, so equal JSON values give equal
|
|
18
|
+
strings."""
|
|
19
|
+
return _json_text.stringify(value, sort_keys=True, default=str)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def frozen(value: object) -> Hashable:
|
|
23
|
+
"""``value`` as a hashable stand-in that is equal for equal values:
|
|
24
|
+
mappings and sequences compare by content."""
|
|
25
|
+
if isinstance(value, Mapping):
|
|
26
|
+
items = cast("Mapping[object, object]", value).items()
|
|
27
|
+
return ("mapping", frozenset((frozen(k), frozen(v)) for k, v in items))
|
|
28
|
+
if isinstance(value, list | tuple):
|
|
29
|
+
return ("sequence", tuple(frozen(item) for item in cast("Sequence[object]", value)))
|
|
30
|
+
if isinstance(value, set | frozenset):
|
|
31
|
+
return ("set", frozenset(frozen(item) for item in cast("set[object]", value)))
|
|
32
|
+
return cast("Hashable", value)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def split_slices(
|
|
36
|
+
result: ExecutionResult, template: DocumentTemplate, prefixes: Sequence[str]
|
|
37
|
+
) -> list[ExecutionResult] | None:
|
|
38
|
+
"""The batched ``result`` split per prefix, with the prefix removed
|
|
39
|
+
from root keys and error paths. ``None`` if an error matches no slice
|
|
40
|
+
or the data is null or missing."""
|
|
41
|
+
slice_errors: list[list[GraphQLError]] = [[] for _ in prefixes]
|
|
42
|
+
for error in result.errors:
|
|
43
|
+
path = error.get("path")
|
|
44
|
+
key = path[0] if path else None
|
|
45
|
+
# No prefix is a prefix of another, so at most one matches.
|
|
46
|
+
index = (
|
|
47
|
+
next((i for i, prefix in enumerate(prefixes) if key.startswith(prefix)), None)
|
|
48
|
+
if isinstance(key, str)
|
|
49
|
+
else None
|
|
50
|
+
)
|
|
51
|
+
if index is None or path is None or not isinstance(key, str):
|
|
52
|
+
return None
|
|
53
|
+
unprefixed: Any = {**error, "path": [key[len(prefixes[index]) :], *path[1:]]}
|
|
54
|
+
slice_errors[index].append(unprefixed)
|
|
55
|
+
data = result.data
|
|
56
|
+
if not isinstance(data, Mapping):
|
|
57
|
+
return None
|
|
58
|
+
data = cast("Mapping[str, object]", data)
|
|
59
|
+
sliced: list[ExecutionResult] = []
|
|
60
|
+
for prefix, errors in zip(prefixes, slice_errors, strict=True):
|
|
61
|
+
slice_data: dict[str, object] = {}
|
|
62
|
+
for selection in template.selections:
|
|
63
|
+
key = cast("str", selection.alias or selection.name)
|
|
64
|
+
if prefix + key in data:
|
|
65
|
+
slice_data[key] = data[prefix + key]
|
|
66
|
+
sliced.append(replace(result, data=slice_data, errors=tuple(errors)))
|
|
67
|
+
return sliced
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True, slots=True)
|
|
71
|
+
class FoundList:
|
|
72
|
+
"""The list at a resolver's path, and whether its parent's
|
|
73
|
+
``pageInfo.hasNextPage`` says there are more."""
|
|
74
|
+
|
|
75
|
+
items: Sequence[object]
|
|
76
|
+
truncated: bool
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _field(value: object, name: str) -> object:
|
|
80
|
+
return cast("Mapping[str, object]", value).get(name) if isinstance(value, Mapping) else None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def find_list(data: object, path: Sequence[str]) -> FoundList | None:
|
|
84
|
+
"""The list at ``path`` in ``data``, or ``None`` when a field on the
|
|
85
|
+
way is null or missing."""
|
|
86
|
+
parent = data
|
|
87
|
+
for name in path[:-1]:
|
|
88
|
+
parent = _field(parent, name)
|
|
89
|
+
items = _field(parent, path[-1])
|
|
90
|
+
if not isinstance(items, list | tuple):
|
|
91
|
+
return None
|
|
92
|
+
page_info = _field(parent, "pageInfo")
|
|
93
|
+
return FoundList(
|
|
94
|
+
items=cast("Sequence[object]", items),
|
|
95
|
+
truncated=_field(page_info, "hasNextPage") is True,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def entry_index(error: GraphQLError, path: Sequence[str], length: int) -> int | None:
|
|
100
|
+
"""The entry an error at ``[*path, i]`` belongs to, if it is one of
|
|
101
|
+
``length`` entries."""
|
|
102
|
+
error_path = error.get("path")
|
|
103
|
+
if error_path is None or len(error_path) <= len(path):
|
|
104
|
+
return None
|
|
105
|
+
if any(error_path[i] != name for i, name in enumerate(path)):
|
|
106
|
+
return None
|
|
107
|
+
index = error_path[len(path)]
|
|
108
|
+
if not isinstance(index, int) or isinstance(index, bool):
|
|
109
|
+
return None
|
|
110
|
+
return index if 0 <= index < length else None
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Joining and slicing a ``DocumentTemplate`` without a GraphQL parser
|
|
2
|
+
(Effect's ``internal/graphql`` printer): ``join_template`` gives the
|
|
3
|
+
document a template was split from, and ``render_slices`` repeats it once
|
|
4
|
+
per alias prefix."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
|
|
11
|
+
from pyfect.graphql.template import DocumentTemplate, Fragment, Parts
|
|
12
|
+
|
|
13
|
+
_WORD_END = re.compile(r'[\w"-]$', re.ASCII)
|
|
14
|
+
_WORD_START = re.compile(r'^[\w"-]', re.ASCII)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _concat(left: str, right: str) -> str:
|
|
18
|
+
"""Two printed pieces, with a space only where two non-punctuators
|
|
19
|
+
would touch (the generator printer's rule)."""
|
|
20
|
+
if _WORD_END.search(left) and _WORD_START.search(right):
|
|
21
|
+
return f"{left} {right}"
|
|
22
|
+
return left + right
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _concat_all(pieces: Sequence[str]) -> str:
|
|
26
|
+
joined = ""
|
|
27
|
+
for piece in pieces:
|
|
28
|
+
joined = _concat(joined, piece)
|
|
29
|
+
return joined
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _render_parts(parts: Parts, prefix: str) -> str:
|
|
33
|
+
return "".join(prefix + part if i % 2 == 1 else part for i, part in enumerate(parts))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _is_shared(fragment: Fragment) -> bool:
|
|
37
|
+
return len(fragment.body) <= 1
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _render(kind: str, name: str, template: DocumentTemplate, prefixes: Sequence[str]) -> str:
|
|
41
|
+
variables = (
|
|
42
|
+
""
|
|
43
|
+
if not template.variables
|
|
44
|
+
else "("
|
|
45
|
+
+ _concat_all(
|
|
46
|
+
[
|
|
47
|
+
f"${prefix}{variable.name}:{variable.body}"
|
|
48
|
+
for prefix in prefixes
|
|
49
|
+
for variable in template.variables
|
|
50
|
+
]
|
|
51
|
+
)
|
|
52
|
+
+ ")"
|
|
53
|
+
)
|
|
54
|
+
selections: list[str] = []
|
|
55
|
+
for prefix in prefixes:
|
|
56
|
+
for selection in template.selections:
|
|
57
|
+
key = (
|
|
58
|
+
selection.alias if prefix == "" else f"{prefix}{selection.alias or selection.name}"
|
|
59
|
+
)
|
|
60
|
+
head = "" if key is None else f"{key}:"
|
|
61
|
+
selections.append(
|
|
62
|
+
f"{head}{selection.name or ''}{_render_parts(selection.body, prefix)}"
|
|
63
|
+
)
|
|
64
|
+
fragments = [
|
|
65
|
+
_concat_all(["fragment", prefix + fragment.name, _render_parts(fragment.body, prefix)])
|
|
66
|
+
for fragment in template.fragments
|
|
67
|
+
for prefix in ([""] if _is_shared(fragment) else prefixes)
|
|
68
|
+
]
|
|
69
|
+
directives = "" if template.directives is None else _render_parts(template.directives, "")
|
|
70
|
+
return _concat_all(
|
|
71
|
+
[kind, name, variables, directives, "{" + _concat_all(selections) + "}", *fragments]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def join_template(kind: str, name: str, template: DocumentTemplate) -> str:
|
|
76
|
+
"""The document ``template`` was split from."""
|
|
77
|
+
return _render(kind, name, template, [""])
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def unsliceable_reason(template: DocumentTemplate) -> str | None:
|
|
81
|
+
"""Why ``template`` cannot be repeated under aliases, if it cannot."""
|
|
82
|
+
if template.directives is not None and len(template.directives) > 1:
|
|
83
|
+
return "its operation directives reference variables"
|
|
84
|
+
if any(selection.name is None for selection in template.selections):
|
|
85
|
+
return "it has a fragment spread or inline fragment at the root"
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def render_slices(kind: str, name: str, template: DocumentTemplate, prefixes: Sequence[str]) -> str:
|
|
90
|
+
"""One operation repeating ``template`` once per prefix: each slice's
|
|
91
|
+
variables, root response keys and per-slice fragments are renamed with
|
|
92
|
+
its prefix; directives and shared fragments appear once. No prefix may
|
|
93
|
+
be a prefix of another. Raises ``ValueError`` for an unsliceable
|
|
94
|
+
template or a renamed fragment taking a shared fragment's name."""
|
|
95
|
+
shared = {fragment.name for fragment in template.fragments if _is_shared(fragment)}
|
|
96
|
+
collision = next(
|
|
97
|
+
(
|
|
98
|
+
prefix + fragment.name
|
|
99
|
+
for fragment in template.fragments
|
|
100
|
+
if not _is_shared(fragment)
|
|
101
|
+
for prefix in prefixes
|
|
102
|
+
if prefix + fragment.name in shared
|
|
103
|
+
),
|
|
104
|
+
None,
|
|
105
|
+
)
|
|
106
|
+
reason = unsliceable_reason(template) or (
|
|
107
|
+
None if collision is None else f"fragment {collision} would collide with a shared fragment"
|
|
108
|
+
)
|
|
109
|
+
if reason is not None:
|
|
110
|
+
raise ValueError(f"Cannot render slices of operation {name}: {reason}")
|
|
111
|
+
return _render(kind, name, template, prefixes)
|
pyfect/graphql/_types.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""What a GraphQL transport takes and gives: a ``GraphQLRequest`` in, an
|
|
2
|
+
``Answer`` (the raw body with its HTTP status and headers) out, read by the
|
|
3
|
+
client into an ``ExecutionResult``."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from collections.abc import Callable, Mapping
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Literal
|
|
10
|
+
|
|
11
|
+
from pyfect import schema as S
|
|
12
|
+
from pyfect.context import Reference, Tag
|
|
13
|
+
from pyfect.effect import Effect
|
|
14
|
+
from pyfect.graphql.client_error import GraphQLError, TransportError
|
|
15
|
+
from pyfect.http.headers import EMPTY as EMPTY_HEADERS
|
|
16
|
+
from pyfect.http.headers import Headers
|
|
17
|
+
from pyfect.http.url_params import EMPTY as EMPTY_URL_PARAMS
|
|
18
|
+
from pyfect.http.url_params import UrlParams
|
|
19
|
+
from pyfect.stream import Stream
|
|
20
|
+
|
|
21
|
+
type Kind = Literal["query", "mutation", "subscription"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
25
|
+
class GraphQLRequest:
|
|
26
|
+
"""One operation to send: the document (``query``), the operation's
|
|
27
|
+
name and its encoded variables, plus per-call headers and URL params.
|
|
28
|
+
Middleware may return a changed copy (``dataclasses.replace``)."""
|
|
29
|
+
|
|
30
|
+
kind: Kind
|
|
31
|
+
query: str
|
|
32
|
+
operation_name: str
|
|
33
|
+
variables: object
|
|
34
|
+
extensions: Mapping[str, object] | None = None
|
|
35
|
+
headers: Headers = field(default=EMPTY_HEADERS)
|
|
36
|
+
url_params: UrlParams = field(default=EMPTY_URL_PARAMS)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def request_body(request: GraphQLRequest) -> dict[str, object]:
|
|
40
|
+
"""The JSON body of a POST and the payload of a graphql-ws
|
|
41
|
+
``subscribe``; ``extensions`` is left out when unset."""
|
|
42
|
+
body: dict[str, object] = {
|
|
43
|
+
"query": request.query,
|
|
44
|
+
"operationName": request.operation_name,
|
|
45
|
+
"variables": request.variables,
|
|
46
|
+
}
|
|
47
|
+
if request.extensions is not None:
|
|
48
|
+
body["extensions"] = request.extensions
|
|
49
|
+
return body
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
53
|
+
class ExecutionResult:
|
|
54
|
+
"""A GraphQL response: ``data`` (``None`` when null or missing), its
|
|
55
|
+
``errors`` and ``extensions``, and the transport's ``status`` and
|
|
56
|
+
``headers`` (metadata, never read from the body)."""
|
|
57
|
+
|
|
58
|
+
data: object = None
|
|
59
|
+
errors: tuple[GraphQLError, ...] = ()
|
|
60
|
+
extensions: Mapping[str, S.JsonValue] | None = None
|
|
61
|
+
status: int | None = None
|
|
62
|
+
headers: Headers | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
66
|
+
class Answer:
|
|
67
|
+
"""What a transport received: the parsed body, and the HTTP status and
|
|
68
|
+
headers when there were any."""
|
|
69
|
+
|
|
70
|
+
body: object
|
|
71
|
+
status: int | None = None
|
|
72
|
+
headers: Headers | None = None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
76
|
+
class GraphQLProtocolService:
|
|
77
|
+
"""A transport: ``execute`` sends a query or mutation, ``subscribe``
|
|
78
|
+
a subscription. ``url`` resolves the endpoint of an operation, when the
|
|
79
|
+
transport has one."""
|
|
80
|
+
|
|
81
|
+
execute: Callable[[GraphQLRequest], Effect[Answer, TransportError]]
|
|
82
|
+
subscribe: Callable[[GraphQLRequest], Stream[Answer, TransportError]]
|
|
83
|
+
url: Callable[[Kind, str], Effect[str]] | None = None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class GraphQLProtocol(Tag[GraphQLProtocolService], key="effect/graphql/GraphQLProtocol"):
|
|
87
|
+
"""The transport the GraphQL client sends through."""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ForcePost(Reference[bool], key="effect/graphql/ForcePost"):
|
|
91
|
+
"""Set while a resolver batch is sent, so a transport that would send
|
|
92
|
+
queries by GET posts it (a batch may outgrow a URL)."""
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def default(cls) -> bool:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass(frozen=True, slots=True)
|
|
100
|
+
class BatchUrl:
|
|
101
|
+
"""The URL a resolver batch was grouped by, and the ``url`` function
|
|
102
|
+
of the transport that resolved it."""
|
|
103
|
+
|
|
104
|
+
resolve: Callable[[Kind, str], Effect[str]]
|
|
105
|
+
url: str
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ResolvedUrl(Reference[BatchUrl | None], key="effect/graphql/ResolvedUrl"):
|
|
109
|
+
"""Carries a ``BatchUrl`` from a resolver batch to the transport, so it
|
|
110
|
+
sends there without resolving the URL again. The client clears it while
|
|
111
|
+
middleware run, so calls they make resolve their own."""
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def default(cls) -> BatchUrl | None:
|
|
115
|
+
return None
|