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,128 @@
|
|
|
1
|
+
"""Struct key transformations (Effect's ``encodeKeys`` and ``extendTo``)::
|
|
2
|
+
|
|
3
|
+
S.Struct(A).pipe(S.encode_keys({"a": "c"})) # {"a": 1} <-> {"c": "1"}
|
|
4
|
+
S.Struct(A).pipe(S.extend_to(B, {"c": lambda a: Some(a["a"])}))
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Callable, Mapping
|
|
10
|
+
from dataclasses import replace
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from pyfect._equal import dedupe
|
|
14
|
+
from pyfect._json_text import stringify
|
|
15
|
+
from pyfect.option import Option, is_some
|
|
16
|
+
from pyfect.schema import ast
|
|
17
|
+
from pyfect.schema._schema import Bottom, Codec
|
|
18
|
+
from pyfect.schema._struct import Struct, StructSchema
|
|
19
|
+
from pyfect.schema._transform import DecodeToSchema, to_type
|
|
20
|
+
from pyfect.schema.transformation import transform
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _renamed(mapping: Mapping[str, str], value: Mapping[str, object]) -> dict[str, object]:
|
|
24
|
+
return {mapping.get(key, key): item for key, item in value.items()}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class EncodeKeys:
|
|
28
|
+
"""``encode_keys(mapping)``: apply with ``.pipe`` to a struct or class."""
|
|
29
|
+
|
|
30
|
+
__slots__ = ("_mapping",)
|
|
31
|
+
|
|
32
|
+
def __init__(self, mapping: Mapping[str, str]) -> None:
|
|
33
|
+
self._mapping = dict(mapping)
|
|
34
|
+
|
|
35
|
+
def __call__[T, RD, RE](
|
|
36
|
+
self, schema: Codec[T, Mapping[str, object], RD, RE], /
|
|
37
|
+
) -> Bottom[T, Mapping[str, object], RD, RE]:
|
|
38
|
+
node = schema.__codec__().ast
|
|
39
|
+
encoded = ast.to_encoded(node)
|
|
40
|
+
if isinstance(encoded, ast.Class):
|
|
41
|
+
encoded = encoded.shape.encoded
|
|
42
|
+
if not isinstance(encoded, ast.Objects):
|
|
43
|
+
msg = f"encode_keys needs a struct or class, got {encoded.tag}"
|
|
44
|
+
raise TypeError(msg)
|
|
45
|
+
unknown = [key for key in self._mapping if all(p.name != key for p in encoded.properties)]
|
|
46
|
+
if unknown:
|
|
47
|
+
msg = f"Unknown keys in encode_keys mapping: {stringify(unknown)}"
|
|
48
|
+
raise ValueError(msg)
|
|
49
|
+
names = [self._mapping.get(str(p.name), p.name) for p in encoded.properties]
|
|
50
|
+
if len(dedupe(names)) != len(names):
|
|
51
|
+
msg = f"Duplicate encoded keys in {stringify(self._mapping)}"
|
|
52
|
+
raise ValueError(msg)
|
|
53
|
+
source = replace(
|
|
54
|
+
encoded,
|
|
55
|
+
properties=tuple(
|
|
56
|
+
replace(p, name=name) for p, name in zip(encoded.properties, names, strict=True)
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
back = {encoded_name: name for name, encoded_name in self._mapping.items()}
|
|
60
|
+
renaming = transform(
|
|
61
|
+
decode=lambda value: _renamed(back, value),
|
|
62
|
+
encode=lambda value: _renamed(self._mapping, value),
|
|
63
|
+
)
|
|
64
|
+
return Bottom(ast.decode_to(source, node, renaming))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def encode_keys(mapping: Mapping[str, str], /) -> EncodeKeys:
|
|
68
|
+
"""The struct's keys renamed on the encoded side, ``{key: encoded
|
|
69
|
+
key}`` (Effect's ``encodeKeys``). Two keys encoding to one name, or a
|
|
70
|
+
mapping key that is not a field, raise ``ValueError``."""
|
|
71
|
+
return EncodeKeys(mapping)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ExtendTo[TD: Mapping[str, object], A: Mapping[str, object]]:
|
|
75
|
+
"""``extend_to(typed_dict, derive)``: apply with ``.pipe`` to a struct."""
|
|
76
|
+
|
|
77
|
+
__slots__ = ("_derive", "_fields", "_typed_dict")
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
typed_dict: type[TD],
|
|
82
|
+
derive: Mapping[str, Callable[[A], Option[object]]],
|
|
83
|
+
fields: Mapping[str, Codec[object, object, Any, Any]],
|
|
84
|
+
) -> None:
|
|
85
|
+
self._typed_dict = typed_dict
|
|
86
|
+
self._derive = dict(derive)
|
|
87
|
+
self._fields = dict(fields)
|
|
88
|
+
|
|
89
|
+
def _decode(self, value: A) -> TD:
|
|
90
|
+
out: dict[str, object] = dict(value)
|
|
91
|
+
for key, derive in self._derive.items():
|
|
92
|
+
derived = derive(value)
|
|
93
|
+
if is_some(derived):
|
|
94
|
+
out[key] = derived.value
|
|
95
|
+
return out # pyright: ignore[reportReturnType] # checked by the target struct
|
|
96
|
+
|
|
97
|
+
def _encode(self, value: TD) -> A:
|
|
98
|
+
out = {key: item for key, item in value.items() if key not in self._derive}
|
|
99
|
+
return out # pyright: ignore[reportReturnType] # checked by the source struct
|
|
100
|
+
|
|
101
|
+
def __call__[RD, RE](
|
|
102
|
+
self, schema: StructSchema[A, RD, RE], /
|
|
103
|
+
) -> DecodeToSchema[TD, Mapping[str, object], RD, RE]:
|
|
104
|
+
kept = {name: to_type(field) for name, field in schema.fields.items()}
|
|
105
|
+
target = Struct(self._typed_dict, **kept, **self._fields)
|
|
106
|
+
new = [key for key in target.fields if key not in kept]
|
|
107
|
+
if set(new) != set(self._derive):
|
|
108
|
+
msg = (
|
|
109
|
+
f"extend_to derives {stringify(list(self._derive))}, new keys are {stringify(new)}"
|
|
110
|
+
)
|
|
111
|
+
raise ValueError(msg)
|
|
112
|
+
transformation = transform(decode=self._decode, encode=self._encode)
|
|
113
|
+
node = ast.decode_to(schema.__codec__().ast, target.__codec__().ast, transformation)
|
|
114
|
+
return DecodeToSchema(node, schema, target)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def extend_to[TD: Mapping[str, object], A: Mapping[str, object]](
|
|
118
|
+
typed_dict: type[TD],
|
|
119
|
+
derive: Mapping[str, Callable[[A], Option[object]]],
|
|
120
|
+
/,
|
|
121
|
+
**fields: Codec[object, object, Any, Any],
|
|
122
|
+
) -> ExtendTo[TD, A]:
|
|
123
|
+
"""The struct decoded into ``typed_dict``, a struct with its keys and
|
|
124
|
+
more, each new key ``derive[key](value)`` when that is ``Some``; the
|
|
125
|
+
new keys are dropped when encoding (Effect's ``extendTo``). A new key's
|
|
126
|
+
schema comes from its annotation unless given in ``fields``; ``derive``
|
|
127
|
+
must have exactly the new keys (else ``ValueError``)."""
|
|
128
|
+
return ExtendTo(typed_dict, derive, fields)
|
pyfect/schema/_tagged.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""Tagged structs and unions (Effect's ``TaggedStruct``, ``tagDefaultOmit``,
|
|
2
|
+
``toTaggedUnion`` and ``TaggedUnion``).
|
|
3
|
+
|
|
4
|
+
A case of a tagged union is a schema whose values hold a literal under the
|
|
5
|
+
tag key: a ``TaggedClass`` (its ``_tag`` attribute) or a struct of a
|
|
6
|
+
TypedDict with a ``_tag: Literal["A"]`` key (a ``TaggedStruct``). The
|
|
7
|
+
union keeps each case by its literal (``cases``, ``guards``,
|
|
8
|
+
``is_any_of``); a value is matched with Python's ``match`` statement,
|
|
9
|
+
which pyright checks for exhaustiveness (``case _: assert_never(shape)``)::
|
|
10
|
+
|
|
11
|
+
Shape = S.TaggedUnion(Circle, Square) # TaggedClass cases
|
|
12
|
+
match shape:
|
|
13
|
+
case Circle(radius=r): ...
|
|
14
|
+
case Square(): ...
|
|
15
|
+
|
|
16
|
+
A TypedDict case is matched on its tag alone (``case {"_tag": "Circle"}:``):
|
|
17
|
+
pyright does not narrow a mapping pattern that also captures keys.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from collections.abc import Callable, Mapping
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from typing import (
|
|
25
|
+
Any,
|
|
26
|
+
Generic,
|
|
27
|
+
Never,
|
|
28
|
+
TypeGuard,
|
|
29
|
+
TypeVar,
|
|
30
|
+
cast,
|
|
31
|
+
get_args,
|
|
32
|
+
get_origin,
|
|
33
|
+
get_type_hints,
|
|
34
|
+
overload,
|
|
35
|
+
)
|
|
36
|
+
from typing import Literal as TypingLiteral
|
|
37
|
+
|
|
38
|
+
from pyfect._js import js_number
|
|
39
|
+
from pyfect.effect import succeed
|
|
40
|
+
from pyfect.schema._constructor_default import tag
|
|
41
|
+
from pyfect.schema._derive import optional_keys
|
|
42
|
+
from pyfect.schema._literals import LiteralValue as LiteralInput
|
|
43
|
+
from pyfect.schema._literals import Union, UnionSchema
|
|
44
|
+
from pyfect.schema._middleware import with_decoding_default_key
|
|
45
|
+
from pyfect.schema._parse import is_
|
|
46
|
+
from pyfect.schema._schema import Codec
|
|
47
|
+
from pyfect.schema._struct import Struct, StructSchema
|
|
48
|
+
from pyfect.schema._transform import DecodeToSchema, to_type
|
|
49
|
+
from pyfect.schema.ast import LiteralValue
|
|
50
|
+
from pyfect.schema.ast._candidates import sentinels
|
|
51
|
+
|
|
52
|
+
# Explicit covariant parameters: a frozen dataclass's fields would make
|
|
53
|
+
# inferred ones invariant (``tests/test_schema_variance.py``).
|
|
54
|
+
TD_co = TypeVar("TD_co", bound=Mapping[str, object], covariant=True)
|
|
55
|
+
A_co = TypeVar("A_co", covariant=True)
|
|
56
|
+
I_co = TypeVar("I_co", covariant=True)
|
|
57
|
+
RD_co = TypeVar("RD_co", covariant=True)
|
|
58
|
+
RE_co = TypeVar("RE_co", covariant=True)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
62
|
+
class TaggedStructSchema(StructSchema[TD_co, RD_co, RE_co], Generic[TD_co, RD_co, RE_co]):
|
|
63
|
+
"""A struct whose ``"_tag"`` key holds ``tag``."""
|
|
64
|
+
|
|
65
|
+
tag: LiteralValue
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@overload
|
|
69
|
+
def TaggedStruct[TD: Mapping[str, object]](
|
|
70
|
+
typed_dict: type[TD], /
|
|
71
|
+
) -> TaggedStructSchema[TD, Never, Never]: ...
|
|
72
|
+
@overload
|
|
73
|
+
def TaggedStruct[TD: Mapping[str, object], RD, RE](
|
|
74
|
+
typed_dict: type[TD], /, **fields: Codec[object, object, RD, RE]
|
|
75
|
+
) -> TaggedStructSchema[TD, RD, RE]: ...
|
|
76
|
+
def TaggedStruct(
|
|
77
|
+
typed_dict: type[Mapping[str, object]], /, **fields: Codec[object, object, object, object]
|
|
78
|
+
) -> TaggedStructSchema[Any, Any, Any]:
|
|
79
|
+
"""``Struct(typed_dict, **fields)`` of a TypedDict whose ``_tag`` key
|
|
80
|
+
is one literal (``_tag: Literal["A"]``), a case of a tagged union
|
|
81
|
+
(Effect's ``TaggedStruct``); ``make`` fills the tag in (``S.tag``)
|
|
82
|
+
unless ``fields`` give the ``_tag`` schema. Any other ``_tag`` raises
|
|
83
|
+
``TypeError``."""
|
|
84
|
+
hint = get_type_hints(typed_dict).get("_tag")
|
|
85
|
+
literals = get_args(hint) if get_origin(hint) is TypingLiteral else ()
|
|
86
|
+
if len(literals) != 1 or "_tag" in optional_keys(typed_dict):
|
|
87
|
+
raise TypeError(f'{typed_dict.__name__} needs a required "_tag" key of one literal')
|
|
88
|
+
struct = Struct(typed_dict, **{"_tag": tag(literals[0]), **fields})
|
|
89
|
+
return TaggedStructSchema(struct.ast, struct.fields, literals[0])
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def tag_default_omit[L: LiteralInput](literal: L, /) -> DecodeToSchema[L, L, Never, Never]:
|
|
93
|
+
"""A ``"_tag"`` field holding ``literal``, filled in when decoding a
|
|
94
|
+
mapping without it and left out when encoding (Effect's
|
|
95
|
+
``tagDefaultOmit``): ``S.Struct(A, _tag=S.tag_default_omit("A"))``;
|
|
96
|
+
``make`` fills it in too (``S.tag``)."""
|
|
97
|
+
return tag(literal).pipe(with_decoding_default_key(succeed(literal), encoding_strategy="omit"))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
101
|
+
class TaggedUnionSchema(UnionSchema[A_co, I_co, RD_co, RE_co], Generic[A_co, I_co, RD_co, RE_co]):
|
|
102
|
+
"""A union of cases told apart by the literal under ``tag``:
|
|
103
|
+
``discriminants`` are the literals in member order, ``cases`` and
|
|
104
|
+
``guards`` the case schema and its type guard by literal."""
|
|
105
|
+
|
|
106
|
+
tag: str
|
|
107
|
+
discriminants: tuple[LiteralValue, ...]
|
|
108
|
+
cases: Mapping[LiteralValue, Codec[A_co, I_co, RD_co, RE_co]]
|
|
109
|
+
guards: Mapping[LiteralValue, Callable[[object], TypeGuard[A_co]]]
|
|
110
|
+
|
|
111
|
+
def is_any_of[A](
|
|
112
|
+
self: TaggedUnionSchema[A, Any, Any, Any], *literals: LiteralValue
|
|
113
|
+
) -> Callable[[A], bool]:
|
|
114
|
+
"""Whether a value of the union is one of the cases of ``literals``."""
|
|
115
|
+
tag = self.tag
|
|
116
|
+
|
|
117
|
+
def test(value: A) -> bool:
|
|
118
|
+
found = _tag_of(value, tag)
|
|
119
|
+
return any(type(found) is type(lit) and found == lit for lit in literals)
|
|
120
|
+
|
|
121
|
+
return test
|
|
122
|
+
|
|
123
|
+
@overload
|
|
124
|
+
def match[A, T](
|
|
125
|
+
self: TaggedUnionSchema[A, Any, Any, Any],
|
|
126
|
+
cases: Mapping[LiteralValue, Callable[[A], T]],
|
|
127
|
+
/,
|
|
128
|
+
) -> Callable[[A], T]: ...
|
|
129
|
+
@overload
|
|
130
|
+
def match[A, T](
|
|
131
|
+
self: TaggedUnionSchema[A, Any, Any, Any],
|
|
132
|
+
value: A,
|
|
133
|
+
cases: Mapping[LiteralValue, Callable[[A], T]],
|
|
134
|
+
/,
|
|
135
|
+
) -> T: ...
|
|
136
|
+
def match(self, *args: Any) -> Any:
|
|
137
|
+
"""``cases``' function for the case ``value`` is; given the cases
|
|
138
|
+
alone, the function of the value."""
|
|
139
|
+
if len(args) == 1:
|
|
140
|
+
cases_only = args[0]
|
|
141
|
+
|
|
142
|
+
def apply(value: Any) -> Any:
|
|
143
|
+
return cases_only[_tag_of(value, self.tag)](value)
|
|
144
|
+
|
|
145
|
+
return apply
|
|
146
|
+
value, cases = args
|
|
147
|
+
return cases[_tag_of(value, self.tag)](value)
|
|
148
|
+
|
|
149
|
+
@overload
|
|
150
|
+
def match_or_else[A, T](
|
|
151
|
+
self: TaggedUnionSchema[A, Any, Any, Any],
|
|
152
|
+
cases: Mapping[LiteralValue, Callable[[A], T]],
|
|
153
|
+
or_else: Callable[[A], T],
|
|
154
|
+
/,
|
|
155
|
+
) -> Callable[[A], T]: ...
|
|
156
|
+
@overload
|
|
157
|
+
def match_or_else[A, T](
|
|
158
|
+
self: TaggedUnionSchema[A, Any, Any, Any],
|
|
159
|
+
value: A,
|
|
160
|
+
cases: Mapping[LiteralValue, Callable[[A], T]],
|
|
161
|
+
or_else: Callable[[A], T],
|
|
162
|
+
/,
|
|
163
|
+
) -> T: ...
|
|
164
|
+
def match_or_else(self, *args: Any) -> Any:
|
|
165
|
+
"""``match``, with ``or_else`` for a case ``cases`` leaves out."""
|
|
166
|
+
if len(args) == 2:
|
|
167
|
+
cases_only, otherwise = args
|
|
168
|
+
|
|
169
|
+
def apply(value: Any) -> Any:
|
|
170
|
+
return cases_only.get(_tag_of(value, self.tag), otherwise)(value)
|
|
171
|
+
|
|
172
|
+
return apply
|
|
173
|
+
value, cases, or_else = args
|
|
174
|
+
return cases.get(_tag_of(value, self.tag), or_else)(value)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _tag_of(value: object, tag: str) -> object:
|
|
178
|
+
if isinstance(value, Mapping):
|
|
179
|
+
return cast("Mapping[object, object]", value).get(tag)
|
|
180
|
+
return getattr(value, tag, None)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _key(literal: object) -> str | None:
|
|
184
|
+
"""A literal's key as a property key (Effect's: ``1`` and ``"1"`` are
|
|
185
|
+
one key); ``None`` for a literal that is no key (a boolean, ``None``)."""
|
|
186
|
+
if isinstance(literal, str):
|
|
187
|
+
return literal
|
|
188
|
+
if isinstance(literal, int | float) and not isinstance(literal, bool):
|
|
189
|
+
return js_number(literal)
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _guard[A](member: Codec[A, object, object, object]) -> Callable[[object], TypeGuard[A]]:
|
|
194
|
+
test = is_(to_type(member))
|
|
195
|
+
|
|
196
|
+
def guard(value: object, /) -> TypeGuard[A]:
|
|
197
|
+
return test(value)
|
|
198
|
+
|
|
199
|
+
return guard
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class ToTaggedUnion:
|
|
203
|
+
"""``to_tagged_union(tag)``: apply with ``.pipe`` to a ``UnionSchema``."""
|
|
204
|
+
|
|
205
|
+
__slots__ = ("_tag",)
|
|
206
|
+
|
|
207
|
+
def __init__(self, tag: str) -> None:
|
|
208
|
+
self._tag = tag
|
|
209
|
+
|
|
210
|
+
def __call__[A, I, RD, RE](
|
|
211
|
+
self, union: UnionSchema[A, I, RD, RE], /
|
|
212
|
+
) -> TaggedUnionSchema[A, I, RD, RE]:
|
|
213
|
+
tag = self._tag
|
|
214
|
+
discriminants: list[LiteralValue] = []
|
|
215
|
+
cases: dict[LiteralValue, Codec[A, I, RD, RE]] = {}
|
|
216
|
+
keys: set[str] = set()
|
|
217
|
+
|
|
218
|
+
def walk(member: Codec[A, I, RD, RE]) -> None:
|
|
219
|
+
if isinstance(member, UnionSchema):
|
|
220
|
+
nested: UnionSchema[A, I, RD, RE] = member
|
|
221
|
+
for inner in nested.members:
|
|
222
|
+
walk(inner)
|
|
223
|
+
return
|
|
224
|
+
found = [lit for key, lit in sentinels(member.__codec__().ast) if key == tag]
|
|
225
|
+
key = _key(found[0]) if found else None
|
|
226
|
+
if key is None:
|
|
227
|
+
raise ValueError("No literal or unique symbol found")
|
|
228
|
+
if key in keys:
|
|
229
|
+
raise ValueError(f"Duplicate discriminant: {key}")
|
|
230
|
+
keys.add(key)
|
|
231
|
+
discriminants.append(found[0])
|
|
232
|
+
cases[found[0]] = member
|
|
233
|
+
|
|
234
|
+
for member in union.members:
|
|
235
|
+
walk(member)
|
|
236
|
+
guards = {literal: _guard(case) for literal, case in cases.items()}
|
|
237
|
+
return TaggedUnionSchema(union.ast, union.members, tag, tuple(discriminants), cases, guards)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def to_tagged_union(tag: str, /) -> ToTaggedUnion:
|
|
241
|
+
"""The union with its cases kept by the literal each holds under
|
|
242
|
+
``tag``, nested unions' cases included (Effect's ``toTaggedUnion``).
|
|
243
|
+
A case without a string or number literal there raises ``ValueError``,
|
|
244
|
+
as does a literal two cases share (``1`` and ``"1"`` are one)."""
|
|
245
|
+
return ToTaggedUnion(tag)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def TaggedUnion[A = Never, I = Never, RD = Never, RE = Never](
|
|
249
|
+
*cases: Codec[A, I, RD, RE],
|
|
250
|
+
) -> TaggedUnionSchema[A, I, RD, RE]:
|
|
251
|
+
"""The union of ``cases`` told apart by ``"_tag"`` (Effect's
|
|
252
|
+
``TaggedUnion``): each a ``TaggedClass``, a ``TaggedStruct`` or any
|
|
253
|
+
schema with a ``"_tag"`` literal."""
|
|
254
|
+
return to_tagged_union("_tag")(Union(*cases))
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""``TemplateLiteral`` and ``TemplateLiteralParser``: strings made of
|
|
2
|
+
ordered parts (Effect's schemas of TypeScript template literal types)::
|
|
3
|
+
|
|
4
|
+
S.TemplateLiteral("/user/", S.Number) # "/user/123"
|
|
5
|
+
S.TemplateLiteralParser("/user/", S.FiniteFromString)
|
|
6
|
+
# decodes "/user/42" to ("/user/", 42)
|
|
7
|
+
|
|
8
|
+
A part is a literal (``str``, ``int`` or ``float``, matched by its text) or
|
|
9
|
+
a schema of strings, numbers or bigints (see ``ast.TemplateLiteral``).
|
|
10
|
+
``TemplateLiteral`` keeps the string and rejects parts with an encoding;
|
|
11
|
+
``TemplateLiteralParser`` decodes the string into a tuple with one value
|
|
12
|
+
per part and takes any part whose encoded side is such a schema. Pyright
|
|
13
|
+
cannot compute a template's string type nor the parser's tuple type: the
|
|
14
|
+
Type is ``str`` and ``tuple[Any, ...]``.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from collections.abc import Sequence
|
|
20
|
+
from dataclasses import dataclass, replace
|
|
21
|
+
from typing import Any, Generic, Never, TypeVar, overload
|
|
22
|
+
|
|
23
|
+
from pyfect._one_or_many import one_or_many
|
|
24
|
+
from pyfect.exit import Exit, Success
|
|
25
|
+
from pyfect.schema import ast
|
|
26
|
+
from pyfect.schema import transformation as T
|
|
27
|
+
from pyfect.schema._codec_links import (
|
|
28
|
+
BIG_INT_TO_STRING,
|
|
29
|
+
FINITE_TO_STRING,
|
|
30
|
+
NUMBER_TO_STRING,
|
|
31
|
+
is_finite_number,
|
|
32
|
+
literal_to_string,
|
|
33
|
+
)
|
|
34
|
+
from pyfect.schema._options import ParseOptions
|
|
35
|
+
from pyfect.schema._schema import Bottom, Codec, is_schema
|
|
36
|
+
from pyfect.schema.ast._template_literal import (
|
|
37
|
+
TemplateLiteral as TemplateLiteralNode,
|
|
38
|
+
)
|
|
39
|
+
from pyfect.schema.ast._template_literal import (
|
|
40
|
+
normalize_encoded,
|
|
41
|
+
segment,
|
|
42
|
+
template_mismatch,
|
|
43
|
+
)
|
|
44
|
+
from pyfect.schema.issue import Issue
|
|
45
|
+
|
|
46
|
+
RD_co = TypeVar("RD_co", covariant=True)
|
|
47
|
+
RE_co = TypeVar("RE_co", covariant=True)
|
|
48
|
+
|
|
49
|
+
type LiteralPart = str | int | float
|
|
50
|
+
"""A literal part, matched by its text (``1`` by ``"1"``)."""
|
|
51
|
+
type TemplateLiteralPart = Codec[Any, Any, Never, Never] | LiteralPart
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _node(part: object) -> ast.AST:
|
|
55
|
+
if is_schema(part):
|
|
56
|
+
return part.__codec__().ast
|
|
57
|
+
return ast.Literal(part) # pyright: ignore[reportArgumentType]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _nodes(parts: Sequence[object]) -> tuple[ast.AST, ...]:
|
|
61
|
+
return tuple(map(_node, parts))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
65
|
+
class TemplateLiteralSchema(Bottom[str, str, Never, Never]):
|
|
66
|
+
"""A template literal; ``parts`` are its parts, as given."""
|
|
67
|
+
|
|
68
|
+
parts: tuple[TemplateLiteralPart, ...]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@overload
|
|
72
|
+
def TemplateLiteral(
|
|
73
|
+
parts: list[TemplateLiteralPart] | tuple[TemplateLiteralPart, ...], /
|
|
74
|
+
) -> TemplateLiteralSchema: ...
|
|
75
|
+
@overload
|
|
76
|
+
def TemplateLiteral(*parts: TemplateLiteralPart) -> TemplateLiteralSchema: ...
|
|
77
|
+
def TemplateLiteral(*parts: Any) -> TemplateLiteralSchema:
|
|
78
|
+
"""The strings made of ``parts``, kept as they are (Effect's
|
|
79
|
+
``TemplateLiteral``): ``Expected a string matching template literal
|
|
80
|
+
parts`` otherwise. The parts are given as arguments or, as in Effect,
|
|
81
|
+
as one list or tuple. A part with an encoding raises ``ValueError``
|
|
82
|
+
(use ``TemplateLiteralParser``)."""
|
|
83
|
+
parts = one_or_many(parts)
|
|
84
|
+
return TemplateLiteralSchema(TemplateLiteralNode(_nodes(parts)), parts)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
88
|
+
class TemplateLiteralParserSchema(
|
|
89
|
+
Bottom[tuple[Any, ...], str, RD_co, RE_co], Generic[RD_co, RE_co]
|
|
90
|
+
):
|
|
91
|
+
"""A template literal parser; ``parts`` are its parts, as given."""
|
|
92
|
+
|
|
93
|
+
parts: tuple[Codec[Any, Any, RD_co, RE_co] | LiteralPart, ...]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@overload
|
|
97
|
+
def TemplateLiteralParser[RD = Never, RE = Never](
|
|
98
|
+
parts: list[Codec[Any, Any, RD, RE] | LiteralPart]
|
|
99
|
+
| tuple[Codec[Any, Any, RD, RE] | LiteralPart, ...],
|
|
100
|
+
/,
|
|
101
|
+
) -> TemplateLiteralParserSchema[RD, RE]: ...
|
|
102
|
+
@overload
|
|
103
|
+
def TemplateLiteralParser[RD = Never, RE = Never](
|
|
104
|
+
*parts: Codec[Any, Any, RD, RE] | LiteralPart,
|
|
105
|
+
) -> TemplateLiteralParserSchema[RD, RE]: ...
|
|
106
|
+
def TemplateLiteralParser(*parts: Any) -> TemplateLiteralParserSchema[Any, Any]:
|
|
107
|
+
"""The strings made of ``parts``, decoded into a tuple of the parts'
|
|
108
|
+
values (Effect's ``TemplateLiteralParser``): a literal part gives its
|
|
109
|
+
literal, a schema part its decoded value. A part may transform (its
|
|
110
|
+
encoded side is the template's part). The split is greedy, so encoding
|
|
111
|
+
a tuple and decoding the string may give another tuple when a segment
|
|
112
|
+
holds a separator."""
|
|
113
|
+
parts = one_or_many(parts)
|
|
114
|
+
return TemplateLiteralParserSchema(template_literal_parser(_nodes(parts)), parts)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ------------------------------------------------------------------ the AST
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def part_from_string(node: ast.AST) -> ast.AST:
|
|
121
|
+
"""``node`` read from its segment's text: a number, bigint or
|
|
122
|
+
non-string literal at its most encoded end decodes from its text
|
|
123
|
+
(Effect's ``partFromString``)."""
|
|
124
|
+
if node.encoding is not None:
|
|
125
|
+
*init, last = node.encoding
|
|
126
|
+
to = part_from_string(last.to)
|
|
127
|
+
return (
|
|
128
|
+
node
|
|
129
|
+
if to is last.to
|
|
130
|
+
else replace(node, encoding=(*init, ast.Link(to, last.transformation)))
|
|
131
|
+
)
|
|
132
|
+
match node:
|
|
133
|
+
case ast.Number():
|
|
134
|
+
link = FINITE_TO_STRING if is_finite_number(node) else NUMBER_TO_STRING
|
|
135
|
+
return replace(node, encoding=(link,))
|
|
136
|
+
case ast.BigInt():
|
|
137
|
+
return replace(node, encoding=(BIG_INT_TO_STRING,))
|
|
138
|
+
case ast.Literal() if not isinstance(node.literal, str):
|
|
139
|
+
return literal_to_string(node)
|
|
140
|
+
case ast.Union():
|
|
141
|
+
types = tuple(map(part_from_string, node.types))
|
|
142
|
+
same = all(a is b for a, b in zip(types, node.types, strict=True))
|
|
143
|
+
return node if same else replace(node, types=types)
|
|
144
|
+
case _:
|
|
145
|
+
return node
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def segments_tuple(nodes: Sequence[ast.AST]) -> ast.Arrays:
|
|
149
|
+
"""A fixed tuple of ``nodes``."""
|
|
150
|
+
return ast.Arrays(tuple(ast.Element(node) for node in nodes))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _join(parts: tuple[str, ...]) -> str:
|
|
154
|
+
return "".join(parts)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def template_literal_parser(parts: Sequence[ast.AST]) -> ast.Arrays:
|
|
158
|
+
"""The tuple of ``parts``' values decoded from the template of their
|
|
159
|
+
encoded sides (Effect's ``templateLiteralParser``)."""
|
|
160
|
+
template = TemplateLiteralNode(normalize_encoded(parts))
|
|
161
|
+
|
|
162
|
+
def decode(text: str, options: ParseOptions) -> Exit[tuple[str, ...], Issue]:
|
|
163
|
+
segments = segment(template, text, options)
|
|
164
|
+
if segments is None:
|
|
165
|
+
return template_mismatch(text, options)
|
|
166
|
+
return Success(tuple(segments))
|
|
167
|
+
|
|
168
|
+
def encode(values: tuple[str, ...], _: ParseOptions) -> Exit[str, Issue]:
|
|
169
|
+
return Success(_join(values))
|
|
170
|
+
|
|
171
|
+
tuple_node = segments_tuple(tuple(map(part_from_string, parts)))
|
|
172
|
+
return ast.decode_to(template, tuple_node, T.transform_effect(decode=decode, encode=encode))
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""``to_codec_array_from_single``: a schema whose arrays also decode from a
|
|
2
|
+
single string (Effect's ``Schema.toCodecArrayFromSingle``), for query
|
|
3
|
+
strings and forms, where a key given once is a string and a key given
|
|
4
|
+
several times a list."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from collections.abc import Sequence
|
|
9
|
+
from dataclasses import dataclass, replace
|
|
10
|
+
|
|
11
|
+
from pyfect.schema import ast
|
|
12
|
+
from pyfect.schema import transformation as T
|
|
13
|
+
from pyfect.schema._schema import Bottom, Codec
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _to_array(value: Sequence[object] | str) -> Sequence[object]:
|
|
17
|
+
return [value] if isinstance(value, str) else value
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _same(value: Sequence[object]) -> Sequence[object]:
|
|
21
|
+
return value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
FROM_SINGLE = T.transform(decode=_to_array, encode=_same)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def to_codec_array_from_single[T_, E, RD, RE](
|
|
28
|
+
schema: Codec[T_, E, RD, RE], /
|
|
29
|
+
) -> Bottom[T_, E, RD, RE]:
|
|
30
|
+
"""``schema`` with every array (in structs, unions and arrays) also
|
|
31
|
+
decoding a single string as a one-element list; encoding is
|
|
32
|
+
unchanged. A comma-separated string is not split."""
|
|
33
|
+
return Bottom(_Walk()(schema.__codec__().ast))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True, slots=True)
|
|
37
|
+
class _WalkedThunk:
|
|
38
|
+
"""A walked ``Suspend``'s target: marks it walked, so walking it again keeps it."""
|
|
39
|
+
|
|
40
|
+
walk: _Walk
|
|
41
|
+
source: ast.Suspend
|
|
42
|
+
|
|
43
|
+
def __call__(self) -> ast.AST:
|
|
44
|
+
return self.walk(self.source.target)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _Walk:
|
|
48
|
+
def __init__(self) -> None:
|
|
49
|
+
self._done: dict[int, ast.AST] = {}
|
|
50
|
+
|
|
51
|
+
def __call__(self, node: ast.AST) -> ast.AST:
|
|
52
|
+
done = self._done.get(id(node))
|
|
53
|
+
if done is not None:
|
|
54
|
+
return done
|
|
55
|
+
out = self._last_link(node)
|
|
56
|
+
self._done[id(node)] = out
|
|
57
|
+
self._done[id(out)] = out
|
|
58
|
+
return out
|
|
59
|
+
|
|
60
|
+
def _last_link(self, node: ast.AST) -> ast.AST:
|
|
61
|
+
if node.encoding is None:
|
|
62
|
+
return self._step(node)
|
|
63
|
+
*init, last = node.encoding
|
|
64
|
+
if last.transformation is FROM_SINGLE:
|
|
65
|
+
return node
|
|
66
|
+
to = self(last.to)
|
|
67
|
+
if to is last.to:
|
|
68
|
+
return node
|
|
69
|
+
return replace(node, encoding=(*init, ast.Link(to, last.transformation)))
|
|
70
|
+
|
|
71
|
+
def _step(self, node: ast.AST) -> ast.AST:
|
|
72
|
+
out = self._children(node)
|
|
73
|
+
if not isinstance(out, ast.Arrays):
|
|
74
|
+
return out
|
|
75
|
+
source = ast.Union(
|
|
76
|
+
(
|
|
77
|
+
ast.Arrays(
|
|
78
|
+
tuple(ast.Element(ast.unknown, e.optional) for e in out.elements),
|
|
79
|
+
tuple(ast.unknown for _ in out.rest),
|
|
80
|
+
),
|
|
81
|
+
ast.string,
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
ensure = ast.decode_to(source, out, FROM_SINGLE)
|
|
85
|
+
return ast.optional_key(ensure) if node.optional_key else ensure
|
|
86
|
+
|
|
87
|
+
def _children(self, node: ast.AST) -> ast.AST:
|
|
88
|
+
match node:
|
|
89
|
+
case ast.Objects():
|
|
90
|
+
properties = tuple(replace(p, type=self(p.type)) for p in node.properties)
|
|
91
|
+
indexes = tuple(replace(i, type=self(i.type)) for i in node.index_signatures)
|
|
92
|
+
same = all(
|
|
93
|
+
a.type is b.type for a, b in zip(properties, node.properties, strict=True)
|
|
94
|
+
) and all(
|
|
95
|
+
a.type is b.type for a, b in zip(indexes, node.index_signatures, strict=True)
|
|
96
|
+
)
|
|
97
|
+
return (
|
|
98
|
+
node if same else replace(node, properties=properties, index_signatures=indexes)
|
|
99
|
+
)
|
|
100
|
+
case ast.Arrays():
|
|
101
|
+
return replace(
|
|
102
|
+
node,
|
|
103
|
+
elements=tuple(replace(e, type=self(e.type)) for e in node.elements),
|
|
104
|
+
rest=tuple(map(self, node.rest)),
|
|
105
|
+
)
|
|
106
|
+
case ast.Union():
|
|
107
|
+
types = tuple(map(self, node.types))
|
|
108
|
+
same = all(a is b for a, b in zip(types, node.types, strict=True))
|
|
109
|
+
return node if same else replace(node, types=types)
|
|
110
|
+
case ast.Suspend() if isinstance(node.thunk, _WalkedThunk):
|
|
111
|
+
return node
|
|
112
|
+
case ast.Suspend():
|
|
113
|
+
return replace(node, thunk=_WalkedThunk(self, node))
|
|
114
|
+
case _:
|
|
115
|
+
return node
|