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/config/_config.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""``Config[A]``: how to read and decode one value from a ``ConfigProvider``.
|
|
2
|
+
|
|
3
|
+
A config resolves in two channels. Data that is simply not there is
|
|
4
|
+
*absent*: an ``Err`` inside a successful evaluation, which ``with_default``
|
|
5
|
+
and ``option`` recover. Data that is there but invalid, and a source that
|
|
6
|
+
cannot be read, are *failures* of the evaluation, which only ``or_else``
|
|
7
|
+
recovers. ``parse`` turns absence into a failure too, so a config run on its
|
|
8
|
+
own fails with ``ConfigError`` either way.
|
|
9
|
+
"""
|
|
10
|
+
# pyright: reportPrivateUsage=false
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections.abc import Callable, Generator, Mapping
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Never, overload, override
|
|
16
|
+
|
|
17
|
+
from pyfect._core import Effect, _Suspend
|
|
18
|
+
from pyfect._effect_base import A_co
|
|
19
|
+
from pyfect.cause import Cause, Fail
|
|
20
|
+
from pyfect.config._error import ConfigError
|
|
21
|
+
from pyfect.config_provider import ConfigProvider, CurrentConfigProvider, Path, SourceError
|
|
22
|
+
from pyfect.effect import fail_cause, succeed, sync
|
|
23
|
+
from pyfect.option import Option, Some
|
|
24
|
+
from pyfect.result import Err, Ok, Result
|
|
25
|
+
from pyfect.schema import SchemaError
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from pyfect._fiber import Fiber
|
|
29
|
+
|
|
30
|
+
type Resolution[A] = Result[A, ConfigError]
|
|
31
|
+
"""``Ok`` when the value was found, ``Err`` when it is absent."""
|
|
32
|
+
|
|
33
|
+
type Evaluate[A] = Callable[[ConfigProvider, Path], Effect[Resolution[A], ConfigError]]
|
|
34
|
+
"""Evaluate a config with a provider under a path prefix (from ``nested``)."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Config(Effect[A_co, ConfigError]):
|
|
38
|
+
"""A recipe for reading a typed value from a ``ConfigProvider``.
|
|
39
|
+
|
|
40
|
+
A config is an effect: running it (``yield from port`` inside
|
|
41
|
+
``fx.gen``) reads the ``CurrentConfigProvider``. ``parse(provider)``
|
|
42
|
+
reads a given one. The methods build new configs (an ``Effect`` method
|
|
43
|
+
that ``Effect``'s ``Config`` module also defines, such as ``option``,
|
|
44
|
+
has the ``Config`` meaning); the constructors are in ``pyfect.config``."""
|
|
45
|
+
|
|
46
|
+
__slots__ = ("_evaluate",)
|
|
47
|
+
|
|
48
|
+
def __init__(self, evaluate: Evaluate[A_co]) -> None:
|
|
49
|
+
self._evaluate = evaluate
|
|
50
|
+
|
|
51
|
+
@override
|
|
52
|
+
def _step(self, fiber: Fiber[Any, Any]) -> Effect[A_co, ConfigError]:
|
|
53
|
+
return self.parse(fiber.get_ref(CurrentConfigProvider))
|
|
54
|
+
|
|
55
|
+
def parse(self, provider: ConfigProvider) -> Effect[A_co, ConfigError]:
|
|
56
|
+
"""Read this config from ``provider``; absence fails too."""
|
|
57
|
+
return self._evaluate(provider, ()).flat_map(_present_or_fail)
|
|
58
|
+
|
|
59
|
+
@override
|
|
60
|
+
def map[B](self, f: Callable[[A_co], B]) -> Config[B]:
|
|
61
|
+
evaluate = self._evaluate
|
|
62
|
+
return Config(lambda provider, prefix: evaluate(provider, prefix).map(lambda r: r.map(f)))
|
|
63
|
+
|
|
64
|
+
@overload
|
|
65
|
+
def flat_map[B](self, f: Callable[[A_co], Config[B]]) -> Config[B]: ...
|
|
66
|
+
@overload
|
|
67
|
+
def flat_map[B, E2 = Never, R2 = Never](
|
|
68
|
+
self, f: Callable[[A_co], Effect[B, E2, R2]]
|
|
69
|
+
) -> Effect[B, ConfigError | E2, R2]: ...
|
|
70
|
+
@override
|
|
71
|
+
def flat_map(self, f: Callable[[A_co], Effect[Any, Any, Any]]) -> Effect[Any, Any, Any]:
|
|
72
|
+
"""Choose the next config from this one's value; it is read under
|
|
73
|
+
the same ``nested`` prefix. Absence of either is absence. A function
|
|
74
|
+
returning a plain effect gives a plain effect, as ``Effect.flat_map``
|
|
75
|
+
does."""
|
|
76
|
+
evaluate = self._evaluate
|
|
77
|
+
|
|
78
|
+
def run(provider: ConfigProvider, prefix: Path) -> Effect[Resolution[Any], Any]:
|
|
79
|
+
def next_config(resolution: Resolution[A_co]) -> Effect[Resolution[Any], Any]:
|
|
80
|
+
match resolution:
|
|
81
|
+
case Ok(value):
|
|
82
|
+
return _evaluate_at(f(value), provider, prefix)
|
|
83
|
+
case Err():
|
|
84
|
+
return succeed(resolution)
|
|
85
|
+
|
|
86
|
+
return evaluate(provider, prefix).flat_map(next_config)
|
|
87
|
+
|
|
88
|
+
return Config(run)
|
|
89
|
+
|
|
90
|
+
def map_effect[B](self, f: Callable[[A_co], Effect[B, ConfigError]]) -> Config[B]:
|
|
91
|
+
"""Transform the value with an effect that may fail (Effect's
|
|
92
|
+
``mapEffect``); its failure is a failure, not absence."""
|
|
93
|
+
evaluate = self._evaluate
|
|
94
|
+
|
|
95
|
+
def next_value(resolution: Resolution[A_co]) -> Effect[Resolution[B], ConfigError]:
|
|
96
|
+
match resolution:
|
|
97
|
+
case Ok(value):
|
|
98
|
+
return f(value).map(Ok)
|
|
99
|
+
case Err():
|
|
100
|
+
return succeed(resolution)
|
|
101
|
+
|
|
102
|
+
return Config(lambda provider, prefix: evaluate(provider, prefix).flat_map(next_value))
|
|
103
|
+
|
|
104
|
+
def or_else[B](self, that: Callable[[ConfigError], Config[B]]) -> Config[A_co | B]:
|
|
105
|
+
"""Read ``that(error)`` when this config is absent or fails with a
|
|
106
|
+
``ConfigError``. Defects and interruptions are not recovered, nor is
|
|
107
|
+
a failure that comes with one."""
|
|
108
|
+
evaluate = self._evaluate
|
|
109
|
+
|
|
110
|
+
def run(
|
|
111
|
+
provider: ConfigProvider, prefix: Path
|
|
112
|
+
) -> Effect[Resolution[A_co | B], ConfigError]:
|
|
113
|
+
def on_failure(cause: Cause[ConfigError]) -> Effect[Resolution[B], ConfigError]:
|
|
114
|
+
match cause.reasons:
|
|
115
|
+
case (Fail(error), *rest) if all(isinstance(r, Fail) for r in rest):
|
|
116
|
+
return that(error)._evaluate(provider, prefix)
|
|
117
|
+
case _:
|
|
118
|
+
return fail_cause(cause)
|
|
119
|
+
|
|
120
|
+
def on_success(
|
|
121
|
+
resolution: Resolution[A_co],
|
|
122
|
+
) -> Effect[Resolution[A_co | B], ConfigError]:
|
|
123
|
+
match resolution:
|
|
124
|
+
case Ok():
|
|
125
|
+
return succeed(resolution)
|
|
126
|
+
case Err(error):
|
|
127
|
+
return that(error)._evaluate(provider, prefix)
|
|
128
|
+
|
|
129
|
+
return evaluate(provider, prefix).match_cause_effect(
|
|
130
|
+
on_failure=on_failure, on_success=on_success
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return Config(run)
|
|
134
|
+
|
|
135
|
+
@overload
|
|
136
|
+
def with_default[B](self: Config[B], default: B) -> Config[B]: ...
|
|
137
|
+
@overload
|
|
138
|
+
def with_default[B]( # pyright: ignore[reportOverlappingOverload]
|
|
139
|
+
self, default: B
|
|
140
|
+
) -> Config[A_co | B]: ...
|
|
141
|
+
def with_default[B](self, default: B) -> Config[A_co | B]:
|
|
142
|
+
"""``default`` when the value is absent. Invalid data and source
|
|
143
|
+
errors still fail. A group from ``all`` is absent as a whole when
|
|
144
|
+
any part is."""
|
|
145
|
+
evaluate = self._evaluate
|
|
146
|
+
present: Resolution[B] = Ok(default)
|
|
147
|
+
|
|
148
|
+
def recover(resolution: Resolution[A_co]) -> Resolution[A_co | B]:
|
|
149
|
+
return present if isinstance(resolution, Err) else resolution
|
|
150
|
+
|
|
151
|
+
return Config(lambda provider, prefix: evaluate(provider, prefix).map(recover))
|
|
152
|
+
|
|
153
|
+
@override
|
|
154
|
+
def option(self) -> Config[Option[A_co]]: # pyright: ignore[reportIncompatibleMethodOverride]
|
|
155
|
+
"""``Some(value)``, or ``None`` when the value is absent (Effect's
|
|
156
|
+
``Config.option``). Invalid data and source errors still fail: this
|
|
157
|
+
is not ``Effect.option``, which would turn them into ``None``."""
|
|
158
|
+
return self.map(Some).with_default(None)
|
|
159
|
+
|
|
160
|
+
def nested(self, name: str) -> Config[A_co]:
|
|
161
|
+
"""Read this config under ``name``: an object level in
|
|
162
|
+
``from_unknown``, a ``name_`` prefix in ``from_env``. The outermost
|
|
163
|
+
``nested`` is the outermost segment."""
|
|
164
|
+
evaluate = self._evaluate
|
|
165
|
+
return Config(lambda provider, prefix: evaluate(provider, (*prefix, name)))
|
|
166
|
+
|
|
167
|
+
@override
|
|
168
|
+
def __repr__(self) -> str:
|
|
169
|
+
return "Config()"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _present_or_fail[A](resolution: Resolution[A]) -> Effect[A, ConfigError]:
|
|
173
|
+
match resolution:
|
|
174
|
+
case Ok(value):
|
|
175
|
+
return succeed(value)
|
|
176
|
+
case Err(error):
|
|
177
|
+
return fail_cause(Cause((Fail(error),)))
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _evaluate_at(
|
|
181
|
+
eff: Effect[Any, Any, Any], provider: ConfigProvider, prefix: Path
|
|
182
|
+
) -> Effect[Resolution[Any], Any]:
|
|
183
|
+
"""A config read under ``prefix``; any other effect run for its value."""
|
|
184
|
+
if isinstance(eff, Config):
|
|
185
|
+
return eff._evaluate(provider, prefix)
|
|
186
|
+
return eff.map(Ok)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def require_config(value: object, name: str) -> None:
|
|
190
|
+
if not isinstance(value, Config):
|
|
191
|
+
raise TypeError(f"{name} must be a Config, got {type(value).__name__}: {value!r}")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def succeed_config[A](value: A) -> Config[A]:
|
|
195
|
+
"""A config that reads nothing and gives ``value``."""
|
|
196
|
+
present: Resolution[A] = Ok(value)
|
|
197
|
+
return Config(lambda _provider, _prefix: succeed(present))
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def fail_config(error: SourceError | SchemaError) -> Config[Never]:
|
|
201
|
+
"""A config that always fails with ``ConfigError(error)``."""
|
|
202
|
+
failure: Effect[Never, ConfigError] = fail_cause(Cause((Fail(ConfigError(error)),)))
|
|
203
|
+
return Config(lambda _provider, _prefix: failure)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def option[A](config: Config[A]) -> Config[Option[A]]:
|
|
207
|
+
"""``config.option()``: ``Some(value)``, or ``None`` when the value is
|
|
208
|
+
absent. Invalid data and source errors still fail."""
|
|
209
|
+
return config.option()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def gen[A](f: Callable[[], Generator[Effect[Any, ConfigError, Never], Any, A]]) -> Config[A]:
|
|
213
|
+
"""Build a config from a generator: ``x = yield from cfg`` reads
|
|
214
|
+
``cfg`` under the result's ``nested`` prefix, and the result is absent
|
|
215
|
+
as soon as one read is. Other effects run as they are (they may fail
|
|
216
|
+
only with ``ConfigError`` and need no services)::
|
|
217
|
+
|
|
218
|
+
@config.gen
|
|
219
|
+
def server():
|
|
220
|
+
host = yield from config.string("HOST")
|
|
221
|
+
port = yield from config.port("PORT").with_default(8080)
|
|
222
|
+
return f"{host}:{port}"
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
def run(provider: ConfigProvider, prefix: Path) -> Effect[Resolution[A], ConfigError]:
|
|
226
|
+
def start() -> Effect[Resolution[A], ConfigError]:
|
|
227
|
+
generator = f()
|
|
228
|
+
|
|
229
|
+
def advance(send: Callable[[], Effect[Any, ConfigError, Never]]) -> Effect[Any, Any]:
|
|
230
|
+
try:
|
|
231
|
+
yielded = send()
|
|
232
|
+
except StopIteration as stop:
|
|
233
|
+
done: Resolution[A] = Ok(stop.value)
|
|
234
|
+
return succeed(done)
|
|
235
|
+
|
|
236
|
+
def next_step(resolution: Resolution[Any]) -> Effect[Any, Any]:
|
|
237
|
+
match resolution:
|
|
238
|
+
case Ok(value):
|
|
239
|
+
return advance(lambda: generator.send(value))
|
|
240
|
+
case Err():
|
|
241
|
+
return succeed(resolution)
|
|
242
|
+
|
|
243
|
+
return _evaluate_at(yielded, provider, prefix).flat_map(next_step)
|
|
244
|
+
|
|
245
|
+
# Closing runs the generator's ``finally`` blocks however the
|
|
246
|
+
# read ends: absent, failed, interrupted or done (a no-op then).
|
|
247
|
+
return advance(lambda: next(generator)).ensuring(sync(generator.close))
|
|
248
|
+
|
|
249
|
+
return _Suspend(start)
|
|
250
|
+
|
|
251
|
+
return Config(run)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def unwrap(wrapped: Config[Any] | Mapping[str, Any]) -> Config[Any]:
|
|
255
|
+
"""A config itself, or a (nested) mapping of configs read as one
|
|
256
|
+
``all_dict`` per level."""
|
|
257
|
+
from pyfect.config._all import all_dict
|
|
258
|
+
|
|
259
|
+
if isinstance(wrapped, Config):
|
|
260
|
+
return wrapped
|
|
261
|
+
return all_dict({key: unwrap(value) for key, value in wrapped.items()})
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""The scalar config constructors. Each reads the string at ``name`` (or at
|
|
2
|
+
the config's own path when ``name`` is omitted) and is absent when there is
|
|
3
|
+
none."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any, overload
|
|
9
|
+
|
|
10
|
+
from pyfect._one_or_many import one_or_many
|
|
11
|
+
from pyfect._url import Url
|
|
12
|
+
from pyfect.byte_size import ByteSize
|
|
13
|
+
from pyfect.config import _scalars
|
|
14
|
+
from pyfect.config._config import Config
|
|
15
|
+
from pyfect.config._leaf import scalar_config
|
|
16
|
+
from pyfect.config._scalars import LiteralValue
|
|
17
|
+
from pyfect.duration import Seconds
|
|
18
|
+
from pyfect.logger import LogLevel
|
|
19
|
+
from pyfect.redacted import Redacted
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def string(name: str | None = None) -> Config[str]:
|
|
23
|
+
"""Any string."""
|
|
24
|
+
return scalar_config(_scalars.string, name)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def non_empty_string(name: str | None = None) -> Config[str]:
|
|
28
|
+
"""A string of at least one character."""
|
|
29
|
+
return scalar_config(_scalars.non_empty_string, name)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def number(name: str | None = None) -> Config[float]:
|
|
33
|
+
"""A number, ``"Infinity"``, ``"-Infinity"`` and ``"NaN"`` included
|
|
34
|
+
(Effect's ``Config.Number``)."""
|
|
35
|
+
return scalar_config(_scalars.number, name)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def finite(name: str | None = None) -> Config[float]:
|
|
39
|
+
"""A finite number."""
|
|
40
|
+
return scalar_config(_scalars.finite, name)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def integer(name: str | None = None) -> Config[int]:
|
|
44
|
+
"""A whole number, read exactly (``"1e3"`` is ``1000``; Effect's
|
|
45
|
+
``Config.Int``)."""
|
|
46
|
+
return scalar_config(_scalars.integer, name)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def port(name: str | None = None) -> Config[int]:
|
|
50
|
+
"""An integer from 1 to 65535."""
|
|
51
|
+
return scalar_config(_scalars.port, name)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def boolean(name: str | None = None) -> Config[bool]:
|
|
55
|
+
"""``true`` / ``yes`` / ``on`` / ``1`` / ``y`` or ``false`` / ``no`` /
|
|
56
|
+
``off`` / ``0`` / ``n``, in lower case."""
|
|
57
|
+
return scalar_config(_scalars.boolean, name)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@overload
|
|
61
|
+
def literal[L: LiteralValue](
|
|
62
|
+
values: list[L] | tuple[L, ...], /, *, name: str | None = None
|
|
63
|
+
) -> Config[L]: ...
|
|
64
|
+
@overload
|
|
65
|
+
def literal[L: LiteralValue](*values: L, name: str | None = None) -> Config[L]: ...
|
|
66
|
+
def literal(*values: Any, name: str | None = None) -> Config[Any]:
|
|
67
|
+
"""One of ``values`` (as arguments or, as in Effect, one list or tuple),
|
|
68
|
+
written as Effect writes literals (``True`` is ``"true"``, ``None`` is
|
|
69
|
+
``"null"``). Annotate the result to keep the literal types:
|
|
70
|
+
``env: Config[Literal["dev", "prod"]] = config.literal("dev", "prod")``."""
|
|
71
|
+
return scalar_config(_scalars.literal(one_or_many(values)), name)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def log_level(name: str | None = None) -> Config[LogLevel]:
|
|
75
|
+
"""A ``LogLevel`` by Effect's name: ``All``, ``Fatal``, ``Error``,
|
|
76
|
+
``Warn``, ``Info``, ``Debug``, ``Trace`` or ``None``."""
|
|
77
|
+
return scalar_config(_scalars.log_level, name)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def duration(name: str | None = None) -> Config[Seconds]:
|
|
81
|
+
"""A duration such as ``"10 seconds"`` or ``"500 millis"`` (``nanos``
|
|
82
|
+
to ``weeks``, singular or plural), or ``"Infinity"`` / ``"-Infinity"``."""
|
|
83
|
+
return scalar_config(_scalars.duration, name)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def byte_size(name: str | None = None) -> Config[ByteSize]:
|
|
87
|
+
"""An exact size such as ``"10 MB"`` or ``"1.5 KiB"``."""
|
|
88
|
+
return scalar_config(_scalars.byte_size, name)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def date_time(name: str | None = None) -> Config[datetime]:
|
|
92
|
+
"""An ISO 8601 date or date-time (read as JavaScript's ``Date`` reads it); one with
|
|
93
|
+
no offset is UTC (Effect's ``Config.Date``)."""
|
|
94
|
+
return scalar_config(_scalars.date_time, name)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def url(name: str | None = None) -> Config[Url]:
|
|
98
|
+
"""An absolute URL with a scheme (and a host for ``http``, ``https``,
|
|
99
|
+
``ws``, ``wss`` and ``ftp``), split by ``urllib.parse.urlsplit`` into a
|
|
100
|
+
``Url``, whose ``str()`` is the URL (``f"{url}/payouts"``)."""
|
|
101
|
+
return scalar_config(_scalars.url, name)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def redacted(name: str | None = None) -> Config[Redacted[str]]:
|
|
105
|
+
"""A string hidden in a ``Redacted``."""
|
|
106
|
+
return scalar_config(_scalars.redacted, name)
|
pyfect/config/_error.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""``ConfigError``: reading configuration failed."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from pyfect.config_provider import SourceError
|
|
8
|
+
from pyfect.errors import TaggedError
|
|
9
|
+
from pyfect.schema import SchemaError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(eq=False)
|
|
13
|
+
class ConfigError(TaggedError):
|
|
14
|
+
"""Configuration could not be read: the source failed (``SourceError``)
|
|
15
|
+
or the data was missing or invalid (``SchemaError``). ``message`` is
|
|
16
|
+
Effect's (``SchemaError(Expected string\\n at ["PORT"])``); the bare
|
|
17
|
+
text is ``error.cause.message``."""
|
|
18
|
+
|
|
19
|
+
cause: SourceError | SchemaError
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def message(self) -> str:
|
|
23
|
+
match self.cause:
|
|
24
|
+
case SchemaError():
|
|
25
|
+
return f"SchemaError({self.cause.message})"
|
|
26
|
+
case SourceError(message=message):
|
|
27
|
+
return f"SourceError: {message}"
|
pyfect/config/_leaf.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Reading one node: provider failures as ``ConfigError``, and the scalar
|
|
2
|
+
configs built on a ``Scalar`` decoder."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Never
|
|
9
|
+
|
|
10
|
+
from pyfect._core import Effect
|
|
11
|
+
from pyfect.cause import Cause, Die, Fail, Interrupt, Reason
|
|
12
|
+
from pyfect.config._config import Config, Resolution
|
|
13
|
+
from pyfect.config._error import ConfigError
|
|
14
|
+
from pyfect.config_provider import ConfigProvider, Node, Path, SourceError
|
|
15
|
+
from pyfect.effect import fail, fail_cause, succeed
|
|
16
|
+
from pyfect.result import Err, Ok, Result
|
|
17
|
+
from pyfect.schema import SchemaError
|
|
18
|
+
from pyfect.schema.ast import Unknown, string
|
|
19
|
+
from pyfect.schema.issue import Composite, InvalidType, Issue, Pointer
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True, slots=True)
|
|
23
|
+
class Scalar[A]:
|
|
24
|
+
"""How a config reads a value from one string: ``missing`` is the issue
|
|
25
|
+
when the node holds no string, ``decode`` parses one."""
|
|
26
|
+
|
|
27
|
+
missing: Issue
|
|
28
|
+
decode: Callable[[str], Result[A, Issue]]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def scalar_config[A](scalar: Scalar[A], name: str | None) -> Config[A]:
|
|
32
|
+
local: Path = () if name is None else (name,)
|
|
33
|
+
|
|
34
|
+
def run(provider: ConfigProvider, prefix: Path) -> Effect[Resolution[A], ConfigError]:
|
|
35
|
+
path = (*prefix, *local)
|
|
36
|
+
|
|
37
|
+
def decode(node: Node | None) -> Effect[Resolution[A], ConfigError]:
|
|
38
|
+
value = None if node is None else node.value
|
|
39
|
+
if value is None:
|
|
40
|
+
return succeed(absent(path, scalar.missing))
|
|
41
|
+
match scalar.decode(value):
|
|
42
|
+
case Ok() as ok:
|
|
43
|
+
return succeed(ok)
|
|
44
|
+
case Err(issue):
|
|
45
|
+
return fail(invalid(path, issue))
|
|
46
|
+
|
|
47
|
+
return load(provider, path).flat_map(decode)
|
|
48
|
+
|
|
49
|
+
return Config(run)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def load(provider: ConfigProvider, path: Path) -> Effect[Node | None, ConfigError]:
|
|
53
|
+
"""``provider.load(path)``, failing with ``ConfigError`` where the
|
|
54
|
+
provider fails, or dies, with a ``SourceError``. Other defects and
|
|
55
|
+
interruptions are kept."""
|
|
56
|
+
return provider.load(path).catch_cause(
|
|
57
|
+
lambda cause: fail_cause(Cause(tuple(map(_source_reason, cause.reasons))))
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _source_reason(reason: Reason[SourceError]) -> Reason[ConfigError]:
|
|
62
|
+
match reason:
|
|
63
|
+
case Fail(error) | Die(SourceError() as error):
|
|
64
|
+
return Fail(ConfigError(error), annotations=reason.annotations)
|
|
65
|
+
case Die() | Interrupt():
|
|
66
|
+
return reason
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def at(path: Path, issue: Issue) -> Issue:
|
|
70
|
+
return Pointer(path, issue) if path else issue
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def invalid(path: Path, issue: Issue) -> ConfigError:
|
|
74
|
+
return ConfigError(SchemaError(at(path, issue)))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def absent(path: Path, issue: Issue) -> Resolution[Never]:
|
|
78
|
+
return Err(invalid(path, issue))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
EXPECTED_STRING = InvalidType(string)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def expected(text: str) -> InvalidType:
|
|
85
|
+
"""``Expected <text>`` for a node with no schema of its own: an
|
|
86
|
+
``Unknown`` node named by its ``identifier``."""
|
|
87
|
+
return InvalidType(Unknown(annotations={"identifier": text}))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def composite(first: Issue, second: Issue) -> Composite:
|
|
91
|
+
"""The issues of two readings of one string."""
|
|
92
|
+
return Composite(string, (first, second))
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""The string decoders behind the scalar constructors, with Effect's
|
|
2
|
+
messages (Effect's StringTree encodings of its schemas)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import math
|
|
7
|
+
import re
|
|
8
|
+
from collections.abc import Callable, Sequence
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
from pyfect import _js_date as js_date
|
|
12
|
+
from pyfect import _json_text
|
|
13
|
+
from pyfect._js import js_number
|
|
14
|
+
from pyfect._url import Url, parse_url
|
|
15
|
+
from pyfect.byte_size import ByteSize
|
|
16
|
+
from pyfect.byte_size import from_string as byte_size_from_string
|
|
17
|
+
from pyfect.config._leaf import EXPECTED_STRING, Scalar, composite, expected
|
|
18
|
+
from pyfect.duration import Seconds, parse_duration_text
|
|
19
|
+
from pyfect.logger import LogLevel
|
|
20
|
+
from pyfect.redacted import Redacted
|
|
21
|
+
from pyfect.result import Err, Ok, Result
|
|
22
|
+
from pyfect.schema.issue import InvalidValue, Issue
|
|
23
|
+
|
|
24
|
+
type LiteralValue = str | int | float | bool | None
|
|
25
|
+
|
|
26
|
+
_STRING = EXPECTED_STRING
|
|
27
|
+
_FINITE = re.compile(r"[+-]?(?:[0-9]+(?:\.[0-9]+)?|\.[0-9]+)(?:[Ee][+-]?[0-9]+)?")
|
|
28
|
+
_NOT_FINITE_STRING = InvalidValue({"message": "Expected a string representing a finite number"})
|
|
29
|
+
_NON_FINITE = {"Infinity": math.inf, "-Infinity": -math.inf, "NaN": math.nan}
|
|
30
|
+
_NON_FINITE_EXPECTED = " | ".join(_json_text.stringify(k) for k in _NON_FINITE)
|
|
31
|
+
_INTEGER = InvalidValue({"message": "Expected an integer"})
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _ok[A](value: A) -> Result[A, Issue]:
|
|
35
|
+
return Ok(value)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
string = Scalar[str](_STRING, _ok)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _non_empty(text: str) -> Result[str, Issue]:
|
|
42
|
+
return (
|
|
43
|
+
Ok(text)
|
|
44
|
+
if text
|
|
45
|
+
else Err(InvalidValue({"message": "Expected a value with a length of at least 1"}))
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
non_empty_string = Scalar(_STRING, _non_empty)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _finite(text: str) -> Result[float, Issue]:
|
|
53
|
+
if not _FINITE.fullmatch(text):
|
|
54
|
+
return Err(_NOT_FINITE_STRING)
|
|
55
|
+
value = float(text)
|
|
56
|
+
return (
|
|
57
|
+
Ok(value)
|
|
58
|
+
if math.isfinite(value)
|
|
59
|
+
else Err(InvalidValue({"message": "Expected a finite number"}))
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
finite = Scalar(_STRING, _finite)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _number(text: str) -> Result[float, Issue]:
|
|
67
|
+
if text in _NON_FINITE:
|
|
68
|
+
return Ok(_NON_FINITE[text])
|
|
69
|
+
if _FINITE.fullmatch(text):
|
|
70
|
+
return Ok(float(text))
|
|
71
|
+
return Err(composite(_NOT_FINITE_STRING, expected(_NON_FINITE_EXPECTED)))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
number = Scalar(expected(f"string | {_NON_FINITE_EXPECTED}"), _number)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
_MAX_SAFE_INTEGER = 2**53 - 1
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _integer(text: str) -> Result[int, Issue]:
|
|
81
|
+
if not _FINITE.fullmatch(text):
|
|
82
|
+
return Err(_NOT_FINITE_STRING)
|
|
83
|
+
number = float(text)
|
|
84
|
+
# As Effect's ``Number`` then ``isSafeInteger``, read through a float.
|
|
85
|
+
if not math.isfinite(number) or number != int(number) or abs(number) > _MAX_SAFE_INTEGER:
|
|
86
|
+
return Err(_INTEGER)
|
|
87
|
+
return Ok(int(number))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
integer = Scalar(_STRING, _integer)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _port(text: str) -> Result[int, Issue]:
|
|
94
|
+
match _integer(text):
|
|
95
|
+
case Ok(value) if not 1 <= value <= 65535:
|
|
96
|
+
return Err(InvalidValue({"message": "Expected a value between 1 and 65535"}))
|
|
97
|
+
case decoded:
|
|
98
|
+
return decoded
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
port = Scalar(_STRING, _port)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _choices[A](pairs: Sequence[tuple[str, A]]) -> Scalar[A]:
|
|
105
|
+
"""A decoder accepting exactly the given spellings, the first match
|
|
106
|
+
winning; missing and invalid input both list them."""
|
|
107
|
+
expected_choice = expected(" | ".join(_json_text.stringify(text) for text, _ in pairs))
|
|
108
|
+
table: dict[str, A] = {}
|
|
109
|
+
for text, value in pairs:
|
|
110
|
+
table.setdefault(text, value)
|
|
111
|
+
|
|
112
|
+
def decode(text: str) -> Result[A, Issue]:
|
|
113
|
+
return Ok(table[text]) if text in table else Err(expected_choice)
|
|
114
|
+
|
|
115
|
+
return Scalar(expected_choice, decode)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
boolean = _choices(
|
|
119
|
+
[(t, True) for t in ("true", "yes", "on", "1", "y")]
|
|
120
|
+
+ [(f, False) for f in ("false", "no", "off", "0", "n")]
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
log_level = _choices([(str(level), level) for level in LogLevel])
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def literal[L: LiteralValue](values: Sequence[L]) -> Scalar[L]:
|
|
127
|
+
return _choices([(_encode_literal(v), v) for v in values])
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _encode_literal(value: LiteralValue) -> str:
|
|
131
|
+
"""The literal as Effect writes it in a string: ``True`` is ``"true"``,
|
|
132
|
+
``None`` is ``"null"``, numbers as JavaScript prints them."""
|
|
133
|
+
match value:
|
|
134
|
+
case bool():
|
|
135
|
+
return "true" if value else "false"
|
|
136
|
+
case None:
|
|
137
|
+
return "null"
|
|
138
|
+
case str():
|
|
139
|
+
return value
|
|
140
|
+
case int() | float():
|
|
141
|
+
return js_number(value)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _duration(text: str) -> Result[Seconds, Issue]:
|
|
145
|
+
parsed = parse_duration_text(text)
|
|
146
|
+
return (
|
|
147
|
+
Err(InvalidValue({"message": "Expected a valid Duration string"}))
|
|
148
|
+
if parsed is None
|
|
149
|
+
else Ok(parsed)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
duration = Scalar(_STRING, _duration)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _byte_size(text: str) -> Result[ByteSize, Issue]:
|
|
157
|
+
size = byte_size_from_string(text)
|
|
158
|
+
return (
|
|
159
|
+
Err(InvalidValue({"message": "Expected a valid ByteSize string"}))
|
|
160
|
+
if size is None
|
|
161
|
+
else Ok(size)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
byte_size = Scalar(_STRING, _byte_size)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _date_time(text: str) -> Result[datetime, Issue]:
|
|
169
|
+
parsed = js_date.parse_input(text)
|
|
170
|
+
if parsed is None:
|
|
171
|
+
return Err(InvalidValue({"message": "Expected a valid Date"}))
|
|
172
|
+
return Ok(parsed)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
date_time = Scalar(_STRING, _date_time)
|
|
176
|
+
|
|
177
|
+
_INVALID_URL = InvalidValue({"message": "Expected a valid URL string"})
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _url(text: str) -> Result[Url, Issue]:
|
|
181
|
+
url = parse_url(text)
|
|
182
|
+
return Err(_INVALID_URL) if url is None else Ok(url)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
url = Scalar(_STRING, _url)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _mapped[A, B](scalar: Scalar[A], f: Callable[[A], B]) -> Scalar[B]:
|
|
189
|
+
decode = scalar.decode
|
|
190
|
+
return Scalar(scalar.missing, lambda text: decode(text).map(f))
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
redacted = _mapped(string, Redacted)
|