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/_schema.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""``config.schema``: a config decoded with a schema (Effect's ``Config.schema``).
|
|
2
|
+
|
|
3
|
+
The schema is first turned into its canonical string-tree codec
|
|
4
|
+
(``S.to_codec_string_tree``); the encoded shape of that codec says what to
|
|
5
|
+
load. Each node of the encoded shape gets one more link, from a *cursor*
|
|
6
|
+
(a provider, a path and the node found there): an object loads its declared
|
|
7
|
+
keys and the record keys an index signature accepts, an array loads its
|
|
8
|
+
items, a scalar reads the string at its own path. The schema's own parser
|
|
9
|
+
then drives the loading, so unions, checks and errors work as they do for
|
|
10
|
+
any input.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections.abc import Callable
|
|
16
|
+
from dataclasses import dataclass, replace
|
|
17
|
+
from typing import Any, Never, cast, final
|
|
18
|
+
|
|
19
|
+
from pyfect._core import Effect
|
|
20
|
+
from pyfect.cause import Cause, Die, Fail, Interrupt, Reason
|
|
21
|
+
from pyfect.config._config import Config, Resolution
|
|
22
|
+
from pyfect.config._error import ConfigError
|
|
23
|
+
from pyfect.config_provider import Array, ConfigProvider, Node, Path, Record, SourceError
|
|
24
|
+
from pyfect.effect import fail, fail_cause, succeed
|
|
25
|
+
from pyfect.result import Err, Ok
|
|
26
|
+
from pyfect.schema import Codec, SchemaError, ast
|
|
27
|
+
from pyfect.schema import transformation as T
|
|
28
|
+
from pyfect.schema._to_codec_string_tree import to_codec_string_tree_ast
|
|
29
|
+
from pyfect.schema.ast._result import NotAnObject
|
|
30
|
+
from pyfect.schema.issue import Issue, Pointer
|
|
31
|
+
|
|
32
|
+
_OPAQUE = "Config.schema does not support opaque StringTree encodings"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@final
|
|
36
|
+
class _Undefined(NotAnObject):
|
|
37
|
+
"""What a cursor reads where the provider has nothing usable: Effect's
|
|
38
|
+
``undefined``, which no schema a config can use accepts."""
|
|
39
|
+
|
|
40
|
+
__slots__ = ()
|
|
41
|
+
|
|
42
|
+
def __repr__(self) -> str:
|
|
43
|
+
return "undefined"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_UNDEFINED = _Undefined()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True, slots=True)
|
|
50
|
+
class _Cursor:
|
|
51
|
+
provider: ConfigProvider
|
|
52
|
+
path: Path
|
|
53
|
+
node: Node | None
|
|
54
|
+
|
|
55
|
+
def __str__(self) -> str:
|
|
56
|
+
return "<configuration>"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def schema[A](codec: Codec[A, object, Never, object], path: str | Path | None = None) -> Config[A]:
|
|
60
|
+
"""A config decoded by ``codec`` from what the provider holds at
|
|
61
|
+
``path`` (one key, or a path of keys and indexes; ``None``: the
|
|
62
|
+
config's own path).
|
|
63
|
+
|
|
64
|
+
The codec's string-tree form says what to load: a struct or record
|
|
65
|
+
reads an object, a tuple or array reads indexed items (``A_0``,
|
|
66
|
+
``A_1`` in ``from_env``), anything else one string; a union loads each
|
|
67
|
+
member by its own shape. Missing input that the codec rejects is
|
|
68
|
+
absent (``with_default`` applies); invalid input fails. A codec with
|
|
69
|
+
an opaque encoding (``S.Unknown``, ``S.Any``, ``S.Json``, anywhere in
|
|
70
|
+
it) raises ``ValueError`` here."""
|
|
71
|
+
root = to_codec_string_tree_ast(codec.__codec__().ast)
|
|
72
|
+
encoded = ast.to_encoded(root)
|
|
73
|
+
decoder = _CursorWalk().recur(root)
|
|
74
|
+
local: Path = () if path is None else (path,) if isinstance(path, str) else path
|
|
75
|
+
|
|
76
|
+
def run(provider: ConfigProvider, prefix: Path) -> Effect[Resolution[A], ConfigError]:
|
|
77
|
+
full = (*prefix, *local)
|
|
78
|
+
|
|
79
|
+
def decode(cursor: _Cursor) -> Effect[Resolution[A], Any]:
|
|
80
|
+
def failed(issue: Issue) -> Effect[Resolution[A], ConfigError]:
|
|
81
|
+
error = ConfigError(SchemaError(Pointer(full, issue) if full else issue))
|
|
82
|
+
if _has_input(encoded, cursor.node):
|
|
83
|
+
return fail(error)
|
|
84
|
+
return succeed(Err(error))
|
|
85
|
+
|
|
86
|
+
return cast("Effect[A, Issue]", ast.to_effect(decoder.parse(cursor, {}))).match_effect(
|
|
87
|
+
on_failure=failed, on_success=lambda value: succeed(Ok(value))
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return _load(provider, full).flat_map(decode).catch_cause(_source_errors)
|
|
91
|
+
|
|
92
|
+
return Config(run)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _load(provider: ConfigProvider, path: Path) -> Effect[_Cursor]:
|
|
96
|
+
"""The cursor at ``path``. A source error travels as a defect, so no
|
|
97
|
+
schema recovers from it; ``schema`` turns it back into a failure."""
|
|
98
|
+
|
|
99
|
+
def to_defect(cause: Cause[SourceError]) -> Effect[Never]:
|
|
100
|
+
return fail_cause(Cause(tuple(map(_as_defect, cause.reasons))))
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
provider.load(path).catch_cause(to_defect).map(lambda node: _Cursor(provider, path, node))
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _as_defect(reason: Reason[SourceError]) -> Reason[Never]:
|
|
108
|
+
match reason:
|
|
109
|
+
case Fail(error):
|
|
110
|
+
return Die(error, annotations=reason.annotations)
|
|
111
|
+
case Die() | Interrupt():
|
|
112
|
+
return reason
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _source_errors(cause: Cause[Any]) -> Effect[Never, ConfigError]:
|
|
116
|
+
return fail_cause(Cause(tuple(map(_as_failure, cause.reasons))))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _as_failure(reason: Reason[Any]) -> Reason[Any]:
|
|
120
|
+
match reason:
|
|
121
|
+
case Die(SourceError() as error):
|
|
122
|
+
return Fail(ConfigError(error), annotations=reason.annotations)
|
|
123
|
+
case _:
|
|
124
|
+
return reason
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _has_input(node: ast.AST, found: Node | None) -> bool:
|
|
128
|
+
"""``found`` holds something the encoded shape ``node`` could read."""
|
|
129
|
+
match node:
|
|
130
|
+
case ast.Objects():
|
|
131
|
+
return isinstance(found, Record)
|
|
132
|
+
case ast.Arrays():
|
|
133
|
+
return isinstance(found, Array)
|
|
134
|
+
case ast.Union():
|
|
135
|
+
return any(_has_input(member, found) for member in node.types)
|
|
136
|
+
case ast.Suspend():
|
|
137
|
+
return _has_input(node.target, found)
|
|
138
|
+
case ast.Class():
|
|
139
|
+
return isinstance(found, Record)
|
|
140
|
+
case _:
|
|
141
|
+
return found is not None and found.value is not None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _is_scalar(node: ast.AST) -> bool:
|
|
145
|
+
match node:
|
|
146
|
+
case ast.Union():
|
|
147
|
+
return all(map(_is_scalar, node.types))
|
|
148
|
+
case ast.Objects() | ast.Arrays() | ast.Suspend() | ast.Class():
|
|
149
|
+
return False
|
|
150
|
+
case _:
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
type _Materialize = Callable[[_Cursor], Effect[object]]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _from_cursor(node: ast.AST, materialize: _Materialize) -> ast.AST:
|
|
158
|
+
"""``node`` decoding from a cursor: ``materialize`` loads what it reads."""
|
|
159
|
+
|
|
160
|
+
def decode(cursor: object, _options: object) -> Effect[object]:
|
|
161
|
+
return materialize(cast("_Cursor", cursor))
|
|
162
|
+
|
|
163
|
+
def encode(value: object, _options: object) -> Effect[object]:
|
|
164
|
+
return succeed(value)
|
|
165
|
+
|
|
166
|
+
return ast.decode_to(ast.unknown, node, T.transform_effect(decode=decode, encode=encode))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _scalar(cursor: _Cursor) -> Effect[object]:
|
|
170
|
+
value = None if cursor.node is None else cursor.node.value
|
|
171
|
+
return succeed(_UNDEFINED if value is None else value)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _child(cursor: _Cursor, segment: str | int) -> Effect[_Cursor]:
|
|
175
|
+
return _load(cursor.provider, (*cursor.path, segment))
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class _CursorWalk:
|
|
179
|
+
"""Effect's ``toConfigCursorAST``: each node of the encoded shape (the
|
|
180
|
+
node, or the last node of its encoding chain) decodes from a cursor."""
|
|
181
|
+
|
|
182
|
+
def __init__(self) -> None:
|
|
183
|
+
self._done: dict[int, tuple[ast.AST, ast.AST]] = {}
|
|
184
|
+
self._seen: set[int] = set()
|
|
185
|
+
|
|
186
|
+
def recur(self, node: ast.AST) -> ast.AST:
|
|
187
|
+
hit = self._done.get(id(node))
|
|
188
|
+
if hit is not None:
|
|
189
|
+
return hit[1]
|
|
190
|
+
self._seen.add(id(node))
|
|
191
|
+
if node.encoding is None:
|
|
192
|
+
out = self._step(node)
|
|
193
|
+
else:
|
|
194
|
+
*init, last = node.encoding
|
|
195
|
+
out = replace(
|
|
196
|
+
node, encoding=(*init, ast.Link(self.recur(last.to), last.transformation))
|
|
197
|
+
)
|
|
198
|
+
self._done[id(node)] = (node, out)
|
|
199
|
+
if isinstance(out, ast.Class) and isinstance(out.shape, _CursorShape):
|
|
200
|
+
# Walk the fields now, with the class memoized, so an opaque
|
|
201
|
+
# encoding raises when the config is made.
|
|
202
|
+
out.shape.start()
|
|
203
|
+
return out
|
|
204
|
+
|
|
205
|
+
def _step(self, node: ast.AST) -> ast.AST:
|
|
206
|
+
match node:
|
|
207
|
+
case ast.Objects():
|
|
208
|
+
properties = tuple(replace(p, type=self.recur(p.type)) for p in node.properties)
|
|
209
|
+
indexes = tuple(
|
|
210
|
+
ast.IndexSignature(i.parameter, self.recur(i.type))
|
|
211
|
+
for i in node.index_signatures
|
|
212
|
+
)
|
|
213
|
+
rebuilt = replace(node, properties=properties, index_signatures=indexes)
|
|
214
|
+
return _from_cursor(rebuilt, _objects(node))
|
|
215
|
+
case ast.Arrays():
|
|
216
|
+
elements = tuple(ast.Element(self.recur(e.type), e.optional) for e in node.elements)
|
|
217
|
+
rest = tuple(map(self.recur, node.rest))
|
|
218
|
+
return _from_cursor(replace(node, elements=elements, rest=rest), _arrays)
|
|
219
|
+
case ast.Union():
|
|
220
|
+
types = tuple(map(self.recur, node.types))
|
|
221
|
+
if not _is_scalar(node):
|
|
222
|
+
return replace(node, types=types)
|
|
223
|
+
case ast.Suspend():
|
|
224
|
+
target = node.target
|
|
225
|
+
# Walk new targets now, so an opaque encoding raises when
|
|
226
|
+
# the config is made.
|
|
227
|
+
if id(target) not in self._seen:
|
|
228
|
+
self.recur(target)
|
|
229
|
+
return replace(node, thunk=lambda: self.recur(target))
|
|
230
|
+
case ast.Declaration() | ast.Any():
|
|
231
|
+
raise ValueError(_OPAQUE)
|
|
232
|
+
case ast.Class():
|
|
233
|
+
return self._class(node)
|
|
234
|
+
case _:
|
|
235
|
+
pass
|
|
236
|
+
return _from_cursor(node, _scalar)
|
|
237
|
+
|
|
238
|
+
def _class(self, node: ast.Class) -> ast.AST:
|
|
239
|
+
# A class reads its fields' struct, as Effect's class declaration
|
|
240
|
+
# links it.
|
|
241
|
+
match node.side:
|
|
242
|
+
case "decode":
|
|
243
|
+
return replace(node, shape=_CursorShape(node.shape, self))
|
|
244
|
+
case "encoded":
|
|
245
|
+
return self.recur(node.shape.encoded)
|
|
246
|
+
case "type" | "encode":
|
|
247
|
+
raise ValueError(_OPAQUE)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _objects(node: ast.Objects) -> _Materialize:
|
|
251
|
+
names = [p.name for p in node.properties if isinstance(p.name, str)]
|
|
252
|
+
|
|
253
|
+
def materialize(cursor: _Cursor) -> Effect[object]:
|
|
254
|
+
found = cursor.node
|
|
255
|
+
if not isinstance(found, Record):
|
|
256
|
+
return succeed(_UNDEFINED)
|
|
257
|
+
keys = list(dict.fromkeys(names))
|
|
258
|
+
if node.index_signatures:
|
|
259
|
+
for key in found.keys:
|
|
260
|
+
if key not in keys and any(i.selects(key) for i in node.index_signatures):
|
|
261
|
+
keys.append(key)
|
|
262
|
+
out: dict[str, _Cursor] = {}
|
|
263
|
+
|
|
264
|
+
def load(i: int) -> Effect[object]:
|
|
265
|
+
if i == len(keys):
|
|
266
|
+
return succeed(out)
|
|
267
|
+
|
|
268
|
+
def keep(child: _Cursor) -> Effect[object]:
|
|
269
|
+
if child.node is not None:
|
|
270
|
+
out[keys[i]] = child
|
|
271
|
+
return load(i + 1)
|
|
272
|
+
|
|
273
|
+
return _child(cursor, keys[i]).flat_map(keep)
|
|
274
|
+
|
|
275
|
+
return load(0)
|
|
276
|
+
|
|
277
|
+
return materialize
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _arrays(cursor: _Cursor) -> Effect[object]:
|
|
281
|
+
found = cursor.node
|
|
282
|
+
if not isinstance(found, Array):
|
|
283
|
+
return succeed(_UNDEFINED)
|
|
284
|
+
out: list[_Cursor] = []
|
|
285
|
+
|
|
286
|
+
def load(i: int) -> Effect[object]:
|
|
287
|
+
if i == found.length:
|
|
288
|
+
return succeed(out)
|
|
289
|
+
|
|
290
|
+
def keep(child: _Cursor) -> Effect[object]:
|
|
291
|
+
out.append(child)
|
|
292
|
+
return load(i + 1)
|
|
293
|
+
|
|
294
|
+
return _child(cursor, i).flat_map(keep)
|
|
295
|
+
|
|
296
|
+
return load(0)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class _CursorShape:
|
|
300
|
+
"""A schema class decoding its fields from a cursor. The fields are
|
|
301
|
+
walked on first use, after the class node is memoized, so a field that
|
|
302
|
+
refers back to the class meets its walked node."""
|
|
303
|
+
|
|
304
|
+
__slots__ = ("_fields", "_started", "base", "walk")
|
|
305
|
+
|
|
306
|
+
def __init__(self, base: ast.ClassShape, walk: _CursorWalk) -> None:
|
|
307
|
+
self.base = base
|
|
308
|
+
self.walk = walk
|
|
309
|
+
self._fields: ast.Objects | None = None
|
|
310
|
+
self._started = False
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
def fields(self) -> ast.Objects:
|
|
314
|
+
if self._fields is None:
|
|
315
|
+
self._started = True
|
|
316
|
+
self._fields = cast("ast.Objects", self.walk.recur(self.base.fields))
|
|
317
|
+
return self._fields
|
|
318
|
+
|
|
319
|
+
def start(self) -> None:
|
|
320
|
+
"""Walk the fields unless that already began."""
|
|
321
|
+
if not self._started:
|
|
322
|
+
self.fields # noqa: B018
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def cls(self) -> type:
|
|
326
|
+
return self.base.cls
|
|
327
|
+
|
|
328
|
+
@property
|
|
329
|
+
def encoded(self) -> ast.AST:
|
|
330
|
+
return self.base.encoded
|
|
331
|
+
|
|
332
|
+
@property
|
|
333
|
+
def checks(self) -> ast.Checks | None:
|
|
334
|
+
return self.base.checks
|
|
335
|
+
|
|
336
|
+
def construct(self, values: dict[object, object]) -> object:
|
|
337
|
+
return self.base.construct(values)
|
|
338
|
+
|
|
339
|
+
def values_of(self, instance: object) -> dict[object, object]:
|
|
340
|
+
return self.base.values_of(instance)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Configuration sources (Effect's ``ConfigProvider``).
|
|
2
|
+
|
|
3
|
+
A ``ConfigProvider`` looks a path up and finds a ``Node``: a ``Value``
|
|
4
|
+
(a string), a ``Record`` (child keys) or an ``Array`` (a length), or
|
|
5
|
+
``None`` when nothing is there::
|
|
6
|
+
|
|
7
|
+
provider = config_provider.from_env(env={"APP_PORT": "3000"})
|
|
8
|
+
provider.load(("APP", "PORT")) # Effect[Node | None, SourceError]: Value("3000")
|
|
9
|
+
|
|
10
|
+
Sources: ``from_env`` (``os.environ`` by default), ``from_env_record``,
|
|
11
|
+
``from_unknown`` (nested dicts and lists), ``from_dot_env_contents`` /
|
|
12
|
+
``from_dot_env`` (``.env`` files), ``from_dir`` (a file per key) and
|
|
13
|
+
``make`` (any lookup function). Combinators are methods: ``or_else``,
|
|
14
|
+
``map_input``, ``nested``, ``constant_case``. ``CurrentConfigProvider`` is
|
|
15
|
+
the provider configuration is read from (``from_env()`` unless a ``layer``
|
|
16
|
+
or ``layer_add`` installs another)."""
|
|
17
|
+
|
|
18
|
+
from pyfect._public import publish
|
|
19
|
+
from pyfect.config_provider._dir import from_dir
|
|
20
|
+
from pyfect.config_provider._dotenv import from_dot_env, from_dot_env_contents
|
|
21
|
+
from pyfect.config_provider._env import from_env, from_env_record
|
|
22
|
+
from pyfect.config_provider._layers import layer, layer_add
|
|
23
|
+
from pyfect.config_provider._node import (
|
|
24
|
+
Array,
|
|
25
|
+
Keys,
|
|
26
|
+
Node,
|
|
27
|
+
Path,
|
|
28
|
+
PathInput,
|
|
29
|
+
Record,
|
|
30
|
+
Segment,
|
|
31
|
+
SourceError,
|
|
32
|
+
Value,
|
|
33
|
+
)
|
|
34
|
+
from pyfect.config_provider._provider import ConfigProvider, CurrentConfigProvider, make
|
|
35
|
+
from pyfect.config_provider._unknown import from_unknown
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"Array",
|
|
39
|
+
"ConfigProvider",
|
|
40
|
+
"CurrentConfigProvider",
|
|
41
|
+
"Keys",
|
|
42
|
+
"Node",
|
|
43
|
+
"Path",
|
|
44
|
+
"PathInput",
|
|
45
|
+
"Record",
|
|
46
|
+
"Segment",
|
|
47
|
+
"SourceError",
|
|
48
|
+
"Value",
|
|
49
|
+
"from_dir",
|
|
50
|
+
"from_dot_env",
|
|
51
|
+
"from_dot_env_contents",
|
|
52
|
+
"from_env",
|
|
53
|
+
"from_env_record",
|
|
54
|
+
"from_unknown",
|
|
55
|
+
"layer",
|
|
56
|
+
"layer_add",
|
|
57
|
+
"make",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
publish(globals())
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""``from_dir``: one file per key, as Kubernetes and Docker mount secrets."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import Generator
|
|
7
|
+
from typing import Any, Never
|
|
8
|
+
|
|
9
|
+
from pyfect import _js
|
|
10
|
+
from pyfect._core import Effect
|
|
11
|
+
from pyfect.config_provider._node import Keys, Node, Path, Record, SourceError, Value
|
|
12
|
+
from pyfect.config_provider._provider import ConfigProvider, make
|
|
13
|
+
from pyfect.effect import fail, gen, succeed
|
|
14
|
+
from pyfect.file_system import FileSystem, FileSystemService
|
|
15
|
+
from pyfect.path import Path as PathTag
|
|
16
|
+
from pyfect.path import PathService
|
|
17
|
+
from pyfect.platform_error import NotFound, PlatformError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def from_dir(
|
|
21
|
+
root_path: str | os.PathLike[str] = "/", *, preserve_empty_strings: bool = False
|
|
22
|
+
) -> Effect[ConfigProvider, Never, PathTag | FileSystem]:
|
|
23
|
+
"""A provider over the files under ``root_path``, read through the
|
|
24
|
+
``FileSystem`` and ``Path`` services: the path ``("db", "password")``
|
|
25
|
+
reads the file ``<root_path>/db/password`` (trimmed) as a value, and a
|
|
26
|
+
directory is a record of its entry names. A path missing both as a file
|
|
27
|
+
and as a directory is absent; any other failure is a ``SourceError``
|
|
28
|
+
holding the ``PlatformError``. A path leading above ``root_path`` (``..``)
|
|
29
|
+
is absent. An empty file is absent unless
|
|
30
|
+
``preserve_empty_strings``. Files are read at each lookup."""
|
|
31
|
+
|
|
32
|
+
root = os.fspath(root_path)
|
|
33
|
+
|
|
34
|
+
def build() -> Generator[Effect[Any, Never, PathTag | FileSystem], Any, ConfigProvider]:
|
|
35
|
+
paths = yield from PathTag()
|
|
36
|
+
fs = yield from FileSystem()
|
|
37
|
+
return make(lambda path: _load(paths, fs, root, path, preserve_empty_strings))
|
|
38
|
+
|
|
39
|
+
return gen(build)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _load(
|
|
43
|
+
paths: PathService,
|
|
44
|
+
fs: FileSystemService,
|
|
45
|
+
root: str,
|
|
46
|
+
path: Path,
|
|
47
|
+
preserve_empty_strings: bool,
|
|
48
|
+
) -> Effect[Node | None, SourceError]:
|
|
49
|
+
# Like Node's path.join, a segment is joined below the root even when
|
|
50
|
+
# it starts with a separator.
|
|
51
|
+
segments = [str(segment).lstrip("/" + paths.sep) for segment in path]
|
|
52
|
+
full = paths.normalize(paths.join(root, *segments))
|
|
53
|
+
# Unlike Effect, a path whose ``..`` segments lead above the root is
|
|
54
|
+
# absent rather than read.
|
|
55
|
+
if segments and paths.resolve_within(root, paths.join(*segments)) is None:
|
|
56
|
+
return succeed(None)
|
|
57
|
+
|
|
58
|
+
def as_file(content: str) -> Node | None:
|
|
59
|
+
value = _js.trim(content)
|
|
60
|
+
return None if value == "" and not preserve_empty_strings else Value(value)
|
|
61
|
+
|
|
62
|
+
def as_directory(entries: list[str]) -> Node | None:
|
|
63
|
+
return Record(Keys(paths.basename(entry) for entry in entries))
|
|
64
|
+
|
|
65
|
+
def after_file(file_error: PlatformError) -> Effect[Node | None, PlatformError]:
|
|
66
|
+
return (
|
|
67
|
+
fs.read_directory(full)
|
|
68
|
+
.map(as_directory)
|
|
69
|
+
.catch_all(lambda dir_error: _absent_or(file_error, dir_error))
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
fs.read_file_string(full)
|
|
74
|
+
.map(as_file)
|
|
75
|
+
.catch_all(after_file)
|
|
76
|
+
.map_error(lambda cause: SourceError(f"Failed to read file at {full}", cause))
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _absent_or(file_error: PlatformError, dir_error: PlatformError) -> Effect[None, PlatformError]:
|
|
81
|
+
file_missing = isinstance(file_error.reason, NotFound)
|
|
82
|
+
if file_missing and isinstance(dir_error.reason, NotFound):
|
|
83
|
+
return succeed(None)
|
|
84
|
+
return fail(dir_error if file_missing else file_error)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""``.env`` files: Effect's parser (the ``dotenv`` package's rules) and its
|
|
2
|
+
``dotenv-expand``-style ``$NAME`` / ``${NAME:-default}`` expansion."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
from pyfect import _js
|
|
10
|
+
from pyfect._core import Effect
|
|
11
|
+
from pyfect.config_provider._env import from_env_record
|
|
12
|
+
from pyfect.config_provider._provider import ConfigProvider
|
|
13
|
+
from pyfect.file_system import FileSystem
|
|
14
|
+
from pyfect.platform_error import PlatformError
|
|
15
|
+
|
|
16
|
+
# Effect's regexes with JavaScript's \s, ".", and multiline "^" / "$".
|
|
17
|
+
_S = f"[{_js.SPACE}]"
|
|
18
|
+
_START = f"(?:\\A|(?<=[{_js.LINE_END}]))"
|
|
19
|
+
_END = f"(?=[{_js.LINE_END}]|\\Z)"
|
|
20
|
+
_LINE = re.compile(
|
|
21
|
+
rf"{_START}{_S}*(?:export{_S}+)?([A-Za-z0-9_.-]+)(?:{_S}*={_S}*?|:{_S}+?)"
|
|
22
|
+
rf"({_S}*'(?:\\'|[^'])*'|{_S}*\"(?:\\\"|[^\"])*\"|{_S}*`(?:\\`|[^`])*`|[^#\r\n]+)?"
|
|
23
|
+
rf"{_S}*(?:#[^{_js.LINE_END}]*)?{_END}"
|
|
24
|
+
)
|
|
25
|
+
_QUOTED = re.compile(rf"{_START}(['\"`])([\s\S]*)\1{_END}")
|
|
26
|
+
_REFERENCE = re.compile(r"\$\{?([A-Za-z0-9_]+)")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def from_dot_env_contents(
|
|
30
|
+
contents: str, *, expand_variables: bool = False, preserve_empty_strings: bool = False
|
|
31
|
+
) -> ConfigProvider:
|
|
32
|
+
"""A provider over the ``NAME=value`` lines of a ``.env`` file, read as
|
|
33
|
+
``from_env_record`` reads them. ``#`` comments, ``export`` prefixes and
|
|
34
|
+
single, double or backtick quotes are understood; ``\\n`` and ``\\r``
|
|
35
|
+
are expanded inside double quotes. With ``expand_variables``, ``$NAME``,
|
|
36
|
+
``${NAME}`` and ``${NAME:-default}`` are replaced by other entries of the
|
|
37
|
+
file (``\\$`` stays a literal ``$``)."""
|
|
38
|
+
env = _parse(contents)
|
|
39
|
+
if expand_variables:
|
|
40
|
+
env = _expand(env)
|
|
41
|
+
return from_env_record(env, preserve_empty_strings=preserve_empty_strings)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def from_dot_env(
|
|
45
|
+
path: str | os.PathLike[str] = ".env",
|
|
46
|
+
*,
|
|
47
|
+
expand_variables: bool = False,
|
|
48
|
+
preserve_empty_strings: bool = False,
|
|
49
|
+
) -> Effect[ConfigProvider, PlatformError, FileSystem]:
|
|
50
|
+
"""Read ``path`` with the ``FileSystem`` service and build
|
|
51
|
+
``from_dot_env_contents`` from it; a file that cannot be read fails
|
|
52
|
+
with the file system's ``PlatformError``."""
|
|
53
|
+
file = os.fspath(path)
|
|
54
|
+
return FileSystem().flat_map(
|
|
55
|
+
lambda fs: fs.read_file_string(file).map(
|
|
56
|
+
lambda contents: from_dot_env_contents(
|
|
57
|
+
contents,
|
|
58
|
+
expand_variables=expand_variables,
|
|
59
|
+
preserve_empty_strings=preserve_empty_strings,
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _parse(contents: str) -> dict[str, str]:
|
|
66
|
+
env: dict[str, str] = {}
|
|
67
|
+
text = re.sub(r"\r\n?", "\n", contents)
|
|
68
|
+
for match in _LINE.finditer(text):
|
|
69
|
+
value = _js.trim(match.group(2) or "")
|
|
70
|
+
quote = value[:1]
|
|
71
|
+
value = _QUOTED.sub(r"\2", value)
|
|
72
|
+
if quote == '"':
|
|
73
|
+
value = value.replace("\\n", "\n").replace("\\r", "\r")
|
|
74
|
+
env[match.group(1)] = value
|
|
75
|
+
return env
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _expand(parsed: dict[str, str]) -> dict[str, str]:
|
|
79
|
+
return {
|
|
80
|
+
key: _interpolate(value, parsed, frozenset({key})).replace("\\$", "$")
|
|
81
|
+
for key, value in parsed.items()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _interpolate(text: str, parsed: dict[str, str], seen: frozenset[str]) -> str:
|
|
86
|
+
"""Replace the references in ``text``. A reference already being
|
|
87
|
+
expanded (a cycle, or a name referring to itself) expands to nothing;
|
|
88
|
+
substituted values are not scanned again."""
|
|
89
|
+
out: list[str] = []
|
|
90
|
+
cursor = 0
|
|
91
|
+
position = 0
|
|
92
|
+
while (match := _REFERENCE.search(text, position)) is not None:
|
|
93
|
+
start = match.start()
|
|
94
|
+
position = match.end()
|
|
95
|
+
if start > 0 and text[start - 1] == "\\":
|
|
96
|
+
continue
|
|
97
|
+
name = match.group(1)
|
|
98
|
+
end = position
|
|
99
|
+
default = ""
|
|
100
|
+
if text.startswith(":-", end):
|
|
101
|
+
end += 2
|
|
102
|
+
default_start = end
|
|
103
|
+
depth = 0
|
|
104
|
+
# Match the default's braces in the source, not in substituted values.
|
|
105
|
+
while end < len(text):
|
|
106
|
+
if text.startswith("${", end):
|
|
107
|
+
depth += 1
|
|
108
|
+
end += 1
|
|
109
|
+
elif text[end] == "}":
|
|
110
|
+
if depth == 0:
|
|
111
|
+
break
|
|
112
|
+
depth -= 1
|
|
113
|
+
end += 1
|
|
114
|
+
default = text[default_start:end]
|
|
115
|
+
if text.startswith("}", end):
|
|
116
|
+
end += 1
|
|
117
|
+
expanded = (
|
|
118
|
+
_interpolate(parsed[name], parsed, seen | {name})
|
|
119
|
+
if name in parsed and name not in seen
|
|
120
|
+
else ""
|
|
121
|
+
)
|
|
122
|
+
out.append(text[cursor:start])
|
|
123
|
+
out.append(expanded or _interpolate(default, parsed, seen))
|
|
124
|
+
cursor = end
|
|
125
|
+
position = end
|
|
126
|
+
out.append(text[cursor:])
|
|
127
|
+
return "".join(out)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""``from_env`` and ``from_env_record``: flat ``NAME=value`` pairs read as a
|
|
2
|
+
tree, each ``_`` in a name starting a new path segment."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from collections.abc import Mapping
|
|
8
|
+
|
|
9
|
+
from pyfect._digits import parse_digits
|
|
10
|
+
from pyfect.config_provider._node import Array, Keys, Node, Path, Record, Value
|
|
11
|
+
from pyfect.config_provider._provider import ConfigProvider, make
|
|
12
|
+
from pyfect.effect import succeed
|
|
13
|
+
|
|
14
|
+
_MAX_INDEX = 2**32 - 1
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
type _Trie = dict[str, _Trie]
|
|
18
|
+
"""Name segments below a path; empty when no name continues past it."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def from_env_record(
|
|
22
|
+
env: Mapping[str, str | None], *, preserve_empty_strings: bool = False
|
|
23
|
+
) -> ConfigProvider:
|
|
24
|
+
"""A provider over ``env``; ``None`` values are skipped. A name is split
|
|
25
|
+
on every ``_`` (``A__B`` has an empty middle segment), and a path also
|
|
26
|
+
matches the name its segments join to, so ``("NODE_ENV",)`` and
|
|
27
|
+
``("NODE", "ENV")`` both find ``NODE_ENV``. A path whose children are
|
|
28
|
+
all array indices (``A_0``, ``A_1``) is an ``Array``; one with other
|
|
29
|
+
children a ``Record``, holding the path's own value if it has one. An
|
|
30
|
+
empty value is absent unless ``preserve_empty_strings``. As in Effect,
|
|
31
|
+
the names are read when the provider is made and the values at each
|
|
32
|
+
lookup."""
|
|
33
|
+
trie = _trie(env)
|
|
34
|
+
return make(lambda path: succeed(_node_at(trie, env, path, preserve_empty_strings)))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def from_env(
|
|
38
|
+
env: Mapping[str, str] | None = None, *, preserve_empty_strings: bool = False
|
|
39
|
+
) -> ConfigProvider:
|
|
40
|
+
"""``from_env_record`` over ``env``, or over a copy of ``os.environ``
|
|
41
|
+
made now (as Effect copies ``process.env``), so later writes to the
|
|
42
|
+
environment are not seen."""
|
|
43
|
+
return from_env_record(
|
|
44
|
+
dict(os.environ) if env is None else env, preserve_empty_strings=preserve_empty_strings
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _trie(env: Mapping[str, str | None]) -> _Trie:
|
|
49
|
+
root: _Trie = {}
|
|
50
|
+
for name, value in env.items():
|
|
51
|
+
if value is None:
|
|
52
|
+
continue
|
|
53
|
+
node = root
|
|
54
|
+
for segment in name.split("_"):
|
|
55
|
+
node = node.setdefault(segment, {})
|
|
56
|
+
return root
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _node_at(
|
|
60
|
+
trie: _Trie, env: Mapping[str, str | None], path: Path, preserve_empty_strings: bool
|
|
61
|
+
) -> Node | None:
|
|
62
|
+
leaf = env.get("_".join(str(seg) for seg in path))
|
|
63
|
+
if leaf == "" and not preserve_empty_strings:
|
|
64
|
+
leaf = None
|
|
65
|
+
node: _Trie | None = trie
|
|
66
|
+
for seg in path:
|
|
67
|
+
node = node.get(str(seg))
|
|
68
|
+
if node is None:
|
|
69
|
+
break
|
|
70
|
+
if not node:
|
|
71
|
+
return None if leaf is None else Value(leaf)
|
|
72
|
+
if all(_is_index(child) for child in node):
|
|
73
|
+
return Array(max(int(child) for child in node) + 1, leaf)
|
|
74
|
+
return Record(Keys(node), leaf)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _is_index(key: str) -> bool:
|
|
78
|
+
index = parse_digits(key, unpadded=True)
|
|
79
|
+
return index is not None and index < _MAX_INDEX
|