pyfect-dev 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyfect/__init__.py +729 -0
- pyfect/_all.py +242 -0
- pyfect/_background.py +28 -0
- pyfect/_binary_codecs.py +124 -0
- pyfect/_cached.py +165 -0
- pyfect/_catch.py +128 -0
- pyfect/_catch_reason.py +574 -0
- pyfect/_concurrency.py +291 -0
- pyfect/_context_effects.py +163 -0
- pyfect/_context_managers.py +76 -0
- pyfect/_core.py +368 -0
- pyfect/_digits.py +71 -0
- pyfect/_effect_args.py +46 -0
- pyfect/_effect_base.py +71 -0
- pyfect/_effect_concurrency.py +133 -0
- pyfect/_effect_eager.py +103 -0
- pyfect/_effect_errors.py +344 -0
- pyfect/_effect_folding.py +110 -0
- pyfect/_effect_hash.py +140 -0
- pyfect/_effect_logging.py +57 -0
- pyfect/_effect_metrics.py +102 -0
- pyfect/_effect_resources.py +179 -0
- pyfect/_effect_services.py +25 -0
- pyfect/_effect_timing.py +199 -0
- pyfect/_effect_tracing.py +99 -0
- pyfect/_equal.py +195 -0
- pyfect/_extras.py +43 -0
- pyfect/_fiber.py +969 -0
- pyfect/_fiber_all.py +134 -0
- pyfect/_fiber_collections.py +106 -0
- pyfect/_fn.py +706 -0
- pyfect/_hamt.py +174 -0
- pyfect/_interrupt_reason.py +33 -0
- pyfect/_isaac.py +149 -0
- pyfect/_iterables.py +231 -0
- pyfect/_js.py +263 -0
- pyfect/_js_date.py +87 -0
- pyfect/_json_text.py +286 -0
- pyfect/_json_value.py +9 -0
- pyfect/_layer_core.py +403 -0
- pyfect/_layer_for_life.py +33 -0
- pyfect/_layer_tracing.py +70 -0
- pyfect/_locked_ref.py +147 -0
- pyfect/_loop_thread.py +152 -0
- pyfect/_metric_ref.py +29 -0
- pyfect/_one_or_many.py +14 -0
- pyfect/_os_wait.py +118 -0
- pyfect/_path_posix.py +129 -0
- pyfect/_primitives.py +434 -0
- pyfect/_public.py +36 -0
- pyfect/_reasons.py +64 -0
- pyfect/_released.py +67 -0
- pyfect/_root.py +87 -0
- pyfect/_round_robin.py +73 -0
- pyfect/_some.py +17 -0
- pyfect/_stack.py +52 -0
- pyfect/_stack_frame.py +114 -0
- pyfect/_string_hash.py +35 -0
- pyfect/_subscriber_queue.py +47 -0
- pyfect/_tags.py +14 -0
- pyfect/_tracer_ref.py +21 -0
- pyfect/_tx_ops.py +77 -0
- pyfect/_unsubscripted.py +23 -0
- pyfect/_url.py +54 -0
- pyfect/_waiters.py +127 -0
- pyfect/_wake_tracking.py +98 -0
- pyfect/_wire_codec.py +123 -0
- pyfect/ai/__init__.py +73 -0
- pyfect/ai/_approvals.py +249 -0
- pyfect/ai/_decision_answers.py +185 -0
- pyfect/ai/_generated_client.py +298 -0
- pyfect/ai/_generated_support.py +84 -0
- pyfect/ai/_mcp/__init__.py +1 -0
- pyfect/ai/_mcp/batch.py +68 -0
- pyfect/ai/_mcp/client_transport.py +412 -0
- pyfect/ai/_mcp/client_wire.py +284 -0
- pyfect/ai/_mcp/core.py +490 -0
- pyfect/ai/_mcp/dispatch.py +1048 -0
- pyfect/ai/_mcp/http.py +462 -0
- pyfect/ai/_mcp/http_admission.py +269 -0
- pyfect/ai/_mcp/http_status.py +27 -0
- pyfect/ai/_mcp/input_wire.py +226 -0
- pyfect/ai/_mcp/log_levels.py +55 -0
- pyfect/ai/_mcp/param_headers.py +63 -0
- pyfect/ai/_mcp/projection.py +248 -0
- pyfect/ai/_mcp/register.py +683 -0
- pyfect/ai/_mcp/reverse_policy.py +155 -0
- pyfect/ai/_mcp/routing_header.py +24 -0
- pyfect/ai/_mcp/sessions.py +114 -0
- pyfect/ai/_mcp/stdio.py +110 -0
- pyfect/ai/_mcp/subscriptions.py +134 -0
- pyfect/ai/_mcp/tool_schema.py +35 -0
- pyfect/ai/_noul_answers.py +64 -0
- pyfect/ai/_openai_all_of.py +137 -0
- pyfect/ai/_openai_errors.py +233 -0
- pyfect/ai/_provider.py +439 -0
- pyfect/ai/_provider_tools.py +129 -0
- pyfect/ai/_stream_resolution.py +231 -0
- pyfect/ai/_structured_output_checks.py +138 -0
- pyfect/ai/_structured_output_codec.py +229 -0
- pyfect/ai/_structured_output_json.py +109 -0
- pyfect/ai/_tool_call_ids.py +80 -0
- pyfect/ai/ai_error.py +785 -0
- pyfect/ai/anthropic/__init__.py +28 -0
- pyfect/ai/anthropic/_errors.py +164 -0
- pyfect/ai/anthropic/_prompt.py +375 -0
- pyfect/ai/anthropic/_response.py +443 -0
- pyfect/ai/anthropic/_schema.py +206 -0
- pyfect/ai/anthropic/_stream.py +377 -0
- pyfect/ai/anthropic/_tools.py +248 -0
- pyfect/ai/anthropic/client.py +289 -0
- pyfect/ai/anthropic/config.py +32 -0
- pyfect/ai/anthropic/error.py +27 -0
- pyfect/ai/anthropic/language_model.py +366 -0
- pyfect/ai/anthropic/telemetry.py +51 -0
- pyfect/ai/anthropic/tool.py +570 -0
- pyfect/ai/anthropic_structured_output.py +138 -0
- pyfect/ai/chat.py +548 -0
- pyfect/ai/cloudflare/__init__.py +15 -0
- pyfect/ai/cloudflare/_errors.py +108 -0
- pyfect/ai/cloudflare/client.py +174 -0
- pyfect/ai/cloudflare/decision_model.py +117 -0
- pyfect/ai/cloudflare/schema.py +144 -0
- pyfect/ai/decision.py +290 -0
- pyfect/ai/decision_model.py +283 -0
- pyfect/ai/embedding_model.py +177 -0
- pyfect/ai/id_generator.py +79 -0
- pyfect/ai/language_model.py +1077 -0
- pyfect/ai/mcp_client.py +682 -0
- pyfect/ai/mcp_client_error.py +223 -0
- pyfect/ai/mcp_protocol.py +205 -0
- pyfect/ai/mcp_schema.py +1300 -0
- pyfect/ai/mcp_server.py +404 -0
- pyfect/ai/model.py +56 -0
- pyfect/ai/openai/__init__.py +49 -0
- pyfect/ai/openai/_errors.py +89 -0
- pyfect/ai/openai/_prompt.py +527 -0
- pyfect/ai/openai/_requests.py +34 -0
- pyfect/ai/openai/_response.py +569 -0
- pyfect/ai/openai/_schema.py +769 -0
- pyfect/ai/openai/_stream.py +771 -0
- pyfect/ai/openai/_tools.py +128 -0
- pyfect/ai/openai/_web_socket.py +295 -0
- pyfect/ai/openai/client.py +296 -0
- pyfect/ai/openai/client_generated.py +123 -0
- pyfect/ai/openai/config.py +34 -0
- pyfect/ai/openai/decision_model.py +177 -0
- pyfect/ai/openai/embedding_model.py +116 -0
- pyfect/ai/openai/error.py +8 -0
- pyfect/ai/openai/generated/__init__.py +7213 -0
- pyfect/ai/openai/generated/_recursive.py +18 -0
- pyfect/ai/openai/generated/_schemas_0.py +1015 -0
- pyfect/ai/openai/generated/_schemas_1.py +1016 -0
- pyfect/ai/openai/generated/_schemas_2.py +1017 -0
- pyfect/ai/openai/generated/_schemas_3.py +615 -0
- pyfect/ai/openai/generated/client.py +5298 -0
- pyfect/ai/openai/generated/types.py +8123 -0
- pyfect/ai/openai/language_model.py +304 -0
- pyfect/ai/openai/telemetry.py +19 -0
- pyfect/ai/openai/tool.py +391 -0
- pyfect/ai/openai_compat/__init__.py +25 -0
- pyfect/ai/openai_compat/_prompt.py +191 -0
- pyfect/ai/openai_compat/_request.py +153 -0
- pyfect/ai/openai_compat/_response.py +407 -0
- pyfect/ai/openai_compat/_schema.py +97 -0
- pyfect/ai/openai_compat/client.py +234 -0
- pyfect/ai/openai_compat/config.py +32 -0
- pyfect/ai/openai_compat/embedding_model.py +139 -0
- pyfect/ai/openai_compat/error.py +7 -0
- pyfect/ai/openai_compat/language_model.py +245 -0
- pyfect/ai/openai_compat/telemetry.py +51 -0
- pyfect/ai/openai_structured_output.py +118 -0
- pyfect/ai/openrouter/__init__.py +36 -0
- pyfect/ai/openrouter/_errors.py +186 -0
- pyfect/ai/openrouter/_prompt.py +272 -0
- pyfect/ai/openrouter/_response.py +226 -0
- pyfect/ai/openrouter/_stream.py +320 -0
- pyfect/ai/openrouter/client.py +283 -0
- pyfect/ai/openrouter/config.py +36 -0
- pyfect/ai/openrouter/decision_model.py +161 -0
- pyfect/ai/openrouter/error.py +23 -0
- pyfect/ai/openrouter/generated/__init__.py +3029 -0
- pyfect/ai/openrouter/generated/_recursive.py +15 -0
- pyfect/ai/openrouter/generated/_schemas_0.py +1015 -0
- pyfect/ai/openrouter/generated/_schemas_1.py +522 -0
- pyfect/ai/openrouter/generated/client.py +1445 -0
- pyfect/ai/openrouter/generated/types.py +4769 -0
- pyfect/ai/openrouter/language_model.py +345 -0
- pyfect/ai/openrouter/schema.py +165 -0
- pyfect/ai/prompt.py +664 -0
- pyfect/ai/response.py +768 -0
- pyfect/ai/response_id_tracker.py +82 -0
- pyfect/ai/telemetry.py +126 -0
- pyfect/ai/tokenizer.py +61 -0
- pyfect/ai/tool.py +536 -0
- pyfect/ai/toolkit.py +490 -0
- pyfect/ai/typesafe/__init__.py +17 -0
- pyfect/ai/typesafe/_errors.py +120 -0
- pyfect/ai/typesafe/client.py +146 -0
- pyfect/ai/typesafe/config.py +34 -0
- pyfect/ai/typesafe/decision_model.py +90 -0
- pyfect/ai/typesafe/schema.py +140 -0
- pyfect/batching/__init__.py +58 -0
- pyfect/batching/_api.py +71 -0
- pyfect/batching/_engine.py +229 -0
- pyfect/batching/_from_effect.py +76 -0
- pyfect/batching/_persisted.py +98 -0
- pyfect/batching/_request.py +133 -0
- pyfect/batching/_resolver.py +477 -0
- pyfect/batching/_with_cache.py +90 -0
- pyfect/big_decimal.py +500 -0
- pyfect/brand.py +114 -0
- pyfect/byte_size.py +354 -0
- pyfect/cache/__init__.py +29 -0
- pyfect/cache/_cache.py +426 -0
- pyfect/cache/_expiry.py +69 -0
- pyfect/cache/_scoped_cache.py +546 -0
- pyfect/case.py +174 -0
- pyfect/cause.py +462 -0
- pyfect/channel/__init__.py +155 -0
- pyfect/channel/_base.py +92 -0
- pyfect/channel/_catch.py +158 -0
- pyfect/channel/_catch_reason.py +572 -0
- pyfect/channel/_channel.py +158 -0
- pyfect/channel/_combinators.py +220 -0
- pyfect/channel/_concurrency_methods.py +217 -0
- pyfect/channel/_concurrent_map.py +118 -0
- pyfect/channel/_constructors.py +349 -0
- pyfect/channel/_context.py +232 -0
- pyfect/channel/_error_methods.py +336 -0
- pyfect/channel/_errors.py +180 -0
- pyfect/channel/_filters.py +225 -0
- pyfect/channel/_lifecycle.py +71 -0
- pyfect/channel/_lifecycle_methods.py +206 -0
- pyfect/channel/_loop.py +100 -0
- pyfect/channel/_merge.py +246 -0
- pyfect/channel/_repeat.py +154 -0
- pyfect/channel/_run.py +149 -0
- pyfect/channel/_run_methods.py +341 -0
- pyfect/channel/_sinks.py +88 -0
- pyfect/channel/_sources.py +238 -0
- pyfect/channel/_text.py +176 -0
- pyfect/channel/_transform_methods.py +415 -0
- pyfect/channel/_upstream.py +67 -0
- pyfect/channel_schema.py +131 -0
- pyfect/cli/__init__.py +79 -0
- pyfect/cli/_ansi.py +82 -0
- pyfect/cli/_command.py +469 -0
- pyfect/cli/_config.py +164 -0
- pyfect/cli/_environment.py +10 -0
- pyfect/cli/_help.py +195 -0
- pyfect/cli/_lexer.py +81 -0
- pyfect/cli/_parser.py +465 -0
- pyfect/cli/_settings.py +85 -0
- pyfect/cli/_suggest.py +33 -0
- pyfect/cli/_width.py +134 -0
- pyfect/cli/_wizard.py +317 -0
- pyfect/cli/argument.py +212 -0
- pyfect/cli/cli_config.py +40 -0
- pyfect/cli/cli_error.py +242 -0
- pyfect/cli/cli_output.py +305 -0
- pyfect/cli/command.py +440 -0
- pyfect/cli/completions/__init__.py +50 -0
- pyfect/cli/completions/_completer.ps1 +190 -0
- pyfect/cli/completions/_descriptor.py +182 -0
- pyfect/cli/completions/bash.py +300 -0
- pyfect/cli/completions/fish.py +164 -0
- pyfect/cli/completions/powershell.py +156 -0
- pyfect/cli/completions/zsh.py +189 -0
- pyfect/cli/flag.py +246 -0
- pyfect/cli/global_flag.py +99 -0
- pyfect/cli/help_doc.py +63 -0
- pyfect/cli/param.py +789 -0
- pyfect/cli/primitive.py +308 -0
- pyfect/cli/prompt/__init__.py +75 -0
- pyfect/cli/prompt/_auto_complete.py +205 -0
- pyfect/cli/prompt/_choices.py +104 -0
- pyfect/cli/prompt/_confirm.py +195 -0
- pyfect/cli/prompt/_core.py +512 -0
- pyfect/cli/prompt/_date.py +173 -0
- pyfect/cli/prompt/_date_parts.py +387 -0
- pyfect/cli/prompt/_file.py +372 -0
- pyfect/cli/prompt/_multi_select.py +204 -0
- pyfect/cli/prompt/_number.py +257 -0
- pyfect/cli/prompt/_select.py +117 -0
- pyfect/cli/prompt/_text.py +239 -0
- pyfect/cli/terminal_capabilities.py +118 -0
- pyfect/clock.py +150 -0
- pyfect/cluster/__init__.py +155 -0
- pyfect/cluster/_cluster_abandon.py +36 -0
- pyfect/cluster/_cluster_layer.py +122 -0
- pyfect/cluster/_entity_activation.py +16 -0
- pyfect/cluster/_entity_manager/__init__.py +22 -0
- pyfect/cluster/_entity_manager/_activation.py +540 -0
- pyfect/cluster/_entity_manager/_core.py +186 -0
- pyfect/cluster/_entity_manager/_decode.py +60 -0
- pyfect/cluster/_entity_manager/_manager.py +423 -0
- pyfect/cluster/_entity_manager/_state.py +113 -0
- pyfect/cluster/_entity_reaper.py +94 -0
- pyfect/cluster/_hash.py +13 -0
- pyfect/cluster/_interruptors.py +88 -0
- pyfect/cluster/_resource_map.py +179 -0
- pyfect/cluster/_resource_ref.py +266 -0
- pyfect/cluster/_shard_lock.py +14 -0
- pyfect/cluster/cluster_cron.py +230 -0
- pyfect/cluster/cluster_error.py +81 -0
- pyfect/cluster/cluster_http.py +210 -0
- pyfect/cluster/cluster_metrics.py +54 -0
- pyfect/cluster/cluster_schema.py +137 -0
- pyfect/cluster/cluster_socket.py +197 -0
- pyfect/cluster/cluster_workflow_engine/__init__.py +24 -0
- pyfect/cluster/cluster_workflow_engine/_engine.py +1099 -0
- pyfect/cluster/cluster_workflow_engine/_entities.py +298 -0
- pyfect/cluster/deliver_at.py +31 -0
- pyfect/cluster/entity/__init__.py +39 -0
- pyfect/cluster/entity/_entity.py +332 -0
- pyfect/cluster/entity/_handlers.py +168 -0
- pyfect/cluster/entity/_keep_alive.py +74 -0
- pyfect/cluster/entity/_queue.py +103 -0
- pyfect/cluster/entity/_request.py +34 -0
- pyfect/cluster/entity/_services.py +23 -0
- pyfect/cluster/entity/_test_client.py +237 -0
- pyfect/cluster/entity_address.py +25 -0
- pyfect/cluster/entity_id.py +20 -0
- pyfect/cluster/entity_proxy.py +161 -0
- pyfect/cluster/entity_proxy_server.py +103 -0
- pyfect/cluster/entity_resource.py +122 -0
- pyfect/cluster/entity_type.py +20 -0
- pyfect/cluster/envelope.py +307 -0
- pyfect/cluster/http_runner.py +254 -0
- pyfect/cluster/k8s_http_client.py +281 -0
- pyfect/cluster/k8s_types.py +1394 -0
- pyfect/cluster/machine_id.py +21 -0
- pyfect/cluster/message.py +219 -0
- pyfect/cluster/message_storage/__init__.py +67 -0
- pyfect/cluster/message_storage/_encoded.py +393 -0
- pyfect/cluster/message_storage/_memory.py +386 -0
- pyfect/cluster/message_storage/_noop.py +109 -0
- pyfect/cluster/message_storage/_service.py +388 -0
- pyfect/cluster/reply.py +236 -0
- pyfect/cluster/runner.py +32 -0
- pyfect/cluster/runner_address.py +23 -0
- pyfect/cluster/runner_health.py +93 -0
- pyfect/cluster/runner_server.py +286 -0
- pyfect/cluster/runner_storage.py +212 -0
- pyfect/cluster/runners/__init__.py +55 -0
- pyfect/cluster/runners/_local.py +460 -0
- pyfect/cluster/runners/_noop.py +57 -0
- pyfect/cluster/runners/_rpc.py +290 -0
- pyfect/cluster/runners/_service.py +113 -0
- pyfect/cluster/shard_id.py +39 -0
- pyfect/cluster/sharding/__init__.py +12 -0
- pyfect/cluster/sharding/_acquisition.py +490 -0
- pyfect/cluster/sharding/_clients.py +271 -0
- pyfect/cluster/sharding/_make.py +388 -0
- pyfect/cluster/sharding/_routing.py +296 -0
- pyfect/cluster/sharding/_runners_sync.py +198 -0
- pyfect/cluster/sharding/_service.py +91 -0
- pyfect/cluster/sharding/_singletons.py +183 -0
- pyfect/cluster/sharding/_state.py +296 -0
- pyfect/cluster/sharding/_storage_loop.py +421 -0
- pyfect/cluster/sharding_config.py +230 -0
- pyfect/cluster/sharding_registration_event.py +28 -0
- pyfect/cluster/single_runner.py +49 -0
- pyfect/cluster/singleton.py +36 -0
- pyfect/cluster/singleton_address.py +20 -0
- pyfect/cluster/snowflake.py +133 -0
- pyfect/cluster/socket_runner.py +67 -0
- pyfect/cluster/sql_message_storage.py +1001 -0
- pyfect/cluster/sql_runner_storage.py +841 -0
- pyfect/cluster/test_runner.py +23 -0
- pyfect/config/__init__.py +93 -0
- pyfect/config/_all.py +96 -0
- pyfect/config/_collections.py +291 -0
- pyfect/config/_config.py +261 -0
- pyfect/config/_constructors.py +106 -0
- pyfect/config/_error.py +27 -0
- pyfect/config/_leaf.py +92 -0
- pyfect/config/_scalars.py +193 -0
- pyfect/config/_schema.py +340 -0
- pyfect/config_provider/__init__.py +60 -0
- pyfect/config_provider/_dir.py +84 -0
- pyfect/config_provider/_dotenv.py +127 -0
- pyfect/config_provider/_env.py +79 -0
- pyfect/config_provider/_layers.py +80 -0
- pyfect/config_provider/_node.py +90 -0
- pyfect/config_provider/_provider.py +118 -0
- pyfect/config_provider/_unknown.py +62 -0
- pyfect/console/__init__.py +63 -0
- pyfect/console/_effects.py +134 -0
- pyfect/console/_live.py +156 -0
- pyfect/console/_service.py +49 -0
- pyfect/console/_table.py +90 -0
- pyfect/context.py +414 -0
- pyfect/cron/__init__.py +30 -0
- pyfect/cron/_model.py +183 -0
- pyfect/cron/_search.py +268 -0
- pyfect/cron/_text.py +202 -0
- pyfect/cron/_wall.py +42 -0
- pyfect/crypto.py +257 -0
- pyfect/date_time/__init__.py +77 -0
- pyfect/date_time/_calendar.py +209 -0
- pyfect/date_time/_current.py +118 -0
- pyfect/date_time/_epoch.py +22 -0
- pyfect/date_time/_iso.py +80 -0
- pyfect/date_time/_zones.py +135 -0
- pyfect/date_time/intl.py +144 -0
- pyfect/deferred.py +145 -0
- pyfect/devtools/__init__.py +24 -0
- pyfect/devtools/client.py +323 -0
- pyfect/devtools/layers.py +32 -0
- pyfect/devtools/schema.py +301 -0
- pyfect/devtools/server.py +72 -0
- pyfect/duration.py +295 -0
- pyfect/effect.py +565 -0
- pyfect/effectable.py +37 -0
- pyfect/encoding/__init__.py +62 -0
- pyfect/encoding/_archive.py +197 -0
- pyfect/encoding/_json_array_scanner.py +477 -0
- pyfect/encoding/base64.py +23 -0
- pyfect/encoding/base64_url.py +19 -0
- pyfect/encoding/encoding_error.py +17 -0
- pyfect/encoding/hex.py +28 -0
- pyfect/encoding/ini.py +188 -0
- pyfect/encoding/json_array.py +246 -0
- pyfect/encoding/ndjson.py +461 -0
- pyfect/encoding/schema_binary/__init__.py +257 -0
- pyfect/encoding/schema_binary/_channels.py +173 -0
- pyfect/encoding/schema_binary/_compile.py +584 -0
- pyfect/encoding/schema_binary/_decode.py +848 -0
- pyfect/encoding/schema_binary/_dictionary.py +90 -0
- pyfect/encoding/schema_binary/_encode.py +838 -0
- pyfect/encoding/schema_binary/_fingerprint.py +168 -0
- pyfect/encoding/schema_binary/_layout.py +350 -0
- pyfect/encoding/schema_binary/_literals.py +47 -0
- pyfect/encoding/schema_binary/_parser.py +163 -0
- pyfect/encoding/schema_binary/_reader.py +148 -0
- pyfect/encoding/schema_binary/_target.py +310 -0
- pyfect/encoding/schema_binary/_values.py +133 -0
- pyfect/encoding/schema_binary/_wire.py +253 -0
- pyfect/encoding/sse.py +476 -0
- pyfect/encoding/tar/__init__.py +396 -0
- pyfect/encoding/tar/_decoder.py +220 -0
- pyfect/encoding/tar/_encoder.py +83 -0
- pyfect/encoding/tar/_header.py +494 -0
- pyfect/encoding/tar/_model.py +82 -0
- pyfect/encoding/toml.py +375 -0
- pyfect/encoding/xml/__init__.py +222 -0
- pyfect/encoding/xml/_chars.py +119 -0
- pyfect/encoding/xml/_collector.py +106 -0
- pyfect/encoding/xml/_inputs.py +126 -0
- pyfect/encoding/xml/_model.py +114 -0
- pyfect/encoding/xml/_parser.py +694 -0
- pyfect/encoding/xml/_scanner.py +180 -0
- pyfect/encoding/xml/_writer.py +238 -0
- pyfect/encoding/yaml.py +739 -0
- pyfect/encoding/zip/__init__.py +392 -0
- pyfect/encoding/zip/_decoder.py +378 -0
- pyfect/encoding/zip/_format.py +122 -0
- pyfect/encoding/zip/_inflate.py +65 -0
- pyfect/encoding/zip/_model.py +86 -0
- pyfect/encoding/zip/_writer.py +311 -0
- pyfect/error_reporter.py +240 -0
- pyfect/errors.py +321 -0
- pyfect/eventlog/__init__.py +77 -0
- pyfect/eventlog/_conflicts.py +94 -0
- pyfect/eventlog/_crypto.py +64 -0
- pyfect/eventlog/_identity.py +26 -0
- pyfect/eventlog/_identity_derivation.py +79 -0
- pyfect/eventlog/_transferable_bytes.py +55 -0
- pyfect/eventlog/_uuid.py +33 -0
- pyfect/eventlog/event.py +115 -0
- pyfect/eventlog/event_group.py +55 -0
- pyfect/eventlog/event_journal.py +630 -0
- pyfect/eventlog/event_log.py +1039 -0
- pyfect/eventlog/event_log_encryption.py +219 -0
- pyfect/eventlog/event_log_message.py +357 -0
- pyfect/eventlog/event_log_remote.py +465 -0
- pyfect/eventlog/event_log_server.py +442 -0
- pyfect/eventlog/event_log_server_encrypted.py +322 -0
- pyfect/eventlog/event_log_server_unencrypted.py +814 -0
- pyfect/eventlog/event_log_session_auth.py +250 -0
- pyfect/eventlog/event_row.py +423 -0
- pyfect/eventlog/sql_event_journal.py +692 -0
- pyfect/eventlog/sql_event_log_server_encrypted.py +440 -0
- pyfect/eventlog/sql_event_log_server_unencrypted.py +568 -0
- pyfect/execution_plan/__init__.py +42 -0
- pyfect/execution_plan/_events.py +128 -0
- pyfect/execution_plan/_model.py +108 -0
- pyfect/execution_plan/_run.py +176 -0
- pyfect/exit.py +205 -0
- pyfect/fiber_handle.py +305 -0
- pyfect/fiber_map.py +350 -0
- pyfect/fiber_set.py +232 -0
- pyfect/file_system/__init__.py +69 -0
- pyfect/file_system/_copy.py +116 -0
- pyfect/file_system/_errors.py +32 -0
- pyfect/file_system/_file.py +177 -0
- pyfect/file_system/_live.py +351 -0
- pyfect/file_system/_model.py +72 -0
- pyfect/file_system/_noop.py +189 -0
- pyfect/file_system/_service.py +336 -0
- pyfect/file_system/_stat.py +69 -0
- pyfect/file_system/_watch.py +107 -0
- pyfect/filters.py +245 -0
- pyfect/graph/__init__.py +101 -0
- pyfect/graph/_all_pairs.py +150 -0
- pyfect/graph/_combine.py +259 -0
- pyfect/graph/_compact.py +143 -0
- pyfect/graph/_components.py +200 -0
- pyfect/graph/_cycles.py +144 -0
- pyfect/graph/_enumerate.py +168 -0
- pyfect/graph/_errors.py +51 -0
- pyfect/graph/_flow.py +164 -0
- pyfect/graph/_forest.py +109 -0
- pyfect/graph/_graph.py +269 -0
- pyfect/graph/_lowlink.py +98 -0
- pyfect/graph/_matching.py +101 -0
- pyfect/graph/_mutate.py +211 -0
- pyfect/graph/_query.py +138 -0
- pyfect/graph/_records.py +119 -0
- pyfect/graph/_render.py +109 -0
- pyfect/graph/_shortest.py +274 -0
- pyfect/graph/_snapshot.py +50 -0
- pyfect/graph/_store.py +136 -0
- pyfect/graph/_traverse.py +289 -0
- pyfect/graph/_view.py +384 -0
- pyfect/graph/_walker.py +70 -0
- pyfect/graphql/__init__.py +87 -0
- pyfect/graphql/_codec.py +117 -0
- pyfect/graphql/_http.py +309 -0
- pyfect/graphql/_schemas.py +39 -0
- pyfect/graphql/_slices.py +110 -0
- pyfect/graphql/_template.py +111 -0
- pyfect/graphql/_types.py +115 -0
- pyfect/graphql/_ws.py +395 -0
- pyfect/graphql/_ws_messages.py +120 -0
- pyfect/graphql/client.py +428 -0
- pyfect/graphql/client_error.py +285 -0
- pyfect/graphql/group.py +142 -0
- pyfect/graphql/middleware.py +98 -0
- pyfect/graphql/operation.py +409 -0
- pyfect/graphql/protocol.py +119 -0
- pyfect/graphql/resolver.py +574 -0
- pyfect/graphql/template.py +61 -0
- pyfect/hash_map.py +317 -0
- pyfect/hash_ring.py +206 -0
- pyfect/hash_set.py +163 -0
- pyfect/http/__init__.py +125 -0
- pyfect/http/_asgi_listener.py +272 -0
- pyfect/http/_authority.py +63 -0
- pyfect/http/_brotli.py +27 -0
- pyfect/http/_compression.py +52 -0
- pyfect/http/_field.py +18 -0
- pyfect/http/_foreign_path.py +27 -0
- pyfect/http/_form_data.py +34 -0
- pyfect/http/_httpx_options.py +120 -0
- pyfect/http/_in_memory.py +76 -0
- pyfect/http/_request_state.py +59 -0
- pyfect/http/_vary.py +14 -0
- pyfect/http/abort.py +65 -0
- pyfect/http/asgi/__init__.py +59 -0
- pyfect/http/asgi/_app.py +155 -0
- pyfect/http/asgi/_http.py +201 -0
- pyfect/http/asgi/_mount.py +263 -0
- pyfect/http/asgi/_scope.py +86 -0
- pyfect/http/asgi/_serve.py +72 -0
- pyfect/http/asgi/_types.py +13 -0
- pyfect/http/asgi/_web_socket.py +325 -0
- pyfect/http/body.py +437 -0
- pyfect/http/client/__init__.py +111 -0
- pyfect/http/client/_circuit_breaker.py +113 -0
- pyfect/http/client/_cookie_jar.py +120 -0
- pyfect/http/client/_make.py +252 -0
- pyfect/http/client/_rate_limit.py +423 -0
- pyfect/http/client/_redirects.py +93 -0
- pyfect/http/client/_retry.py +115 -0
- pyfect/http/client/_service.py +497 -0
- pyfect/http/cookies.py +444 -0
- pyfect/http/error.py +188 -0
- pyfect/http/etag.py +96 -0
- pyfect/http/file_like.py +49 -0
- pyfect/http/find_my_way/__init__.py +10 -0
- pyfect/http/find_my_way/_nodes.py +186 -0
- pyfect/http/find_my_way/_paths.py +43 -0
- pyfect/http/find_my_way/_query_string.py +67 -0
- pyfect/http/find_my_way/_router.py +401 -0
- pyfect/http/headers.py +343 -0
- pyfect/http/http_effect.py +198 -0
- pyfect/http/httpx_client.py +291 -0
- pyfect/http/hypercorn_server.py +122 -0
- pyfect/http/incoming_message.py +140 -0
- pyfect/http/live_server/__init__.py +191 -0
- pyfect/http/live_server/_body.py +164 -0
- pyfect/http/live_server/_exchange.py +321 -0
- pyfect/http/live_server/_head.py +194 -0
- pyfect/http/live_server/_inbound.py +156 -0
- pyfect/http/live_server/_listener.py +211 -0
- pyfect/http/live_server/_serve.py +225 -0
- pyfect/http/live_server/_timers.py +20 -0
- pyfect/http/live_server/_upgrade.py +108 -0
- pyfect/http/live_server/_websocket.py +286 -0
- pyfect/http/live_server/_ws_frames.py +242 -0
- pyfect/http/method.py +30 -0
- pyfect/http/middleware/__init__.py +61 -0
- pyfect/http/middleware/_compression.py +156 -0
- pyfect/http/middleware/_cors.py +129 -0
- pyfect/http/middleware/_decompression.py +274 -0
- pyfect/http/middleware/_forwarded.py +113 -0
- pyfect/http/middleware/_logger.py +73 -0
- pyfect/http/middleware/_search_params.py +34 -0
- pyfect/http/middleware/_tracer.py +153 -0
- pyfect/http/middleware/_types.py +16 -0
- pyfect/http/mime/__init__.py +69 -0
- pyfect/http/mime/_types.py +361 -0
- pyfect/http/multipart.py +613 -0
- pyfect/http/multipart_parser/__init__.py +348 -0
- pyfect/http/multipart_parser/_content_type.py +109 -0
- pyfect/http/multipart_parser/_headers.py +206 -0
- pyfect/http/multipart_parser/_search.py +85 -0
- pyfect/http/origin.py +146 -0
- pyfect/http/platform.py +593 -0
- pyfect/http/prometheus.py +34 -0
- pyfect/http/request.py +384 -0
- pyfect/http/response.py +474 -0
- pyfect/http/router/__init__.py +82 -0
- pyfect/http/router/_api.py +462 -0
- pyfect/http/router/_in_memory.py +76 -0
- pyfect/http/router/_markers.py +47 -0
- pyfect/http/router/_middleware.py +181 -0
- pyfect/http/router/_middleware_stack.py +51 -0
- pyfect/http/router/_provided.py +31 -0
- pyfect/http/router/_route.py +114 -0
- pyfect/http/router/_router.py +308 -0
- pyfect/http/schema.py +276 -0
- pyfect/http/server.py +199 -0
- pyfect/http/server_error.py +261 -0
- pyfect/http/server_request.py +729 -0
- pyfect/http/server_respondable.py +77 -0
- pyfect/http/server_response.py +569 -0
- pyfect/http/static_server/__init__.py +257 -0
- pyfect/http/static_server/_conditional.py +72 -0
- pyfect/http/static_server/_mime.py +55 -0
- pyfect/http/static_server/_range.py +53 -0
- pyfect/http/status.py +51 -0
- pyfect/http/template.py +161 -0
- pyfect/http/trace_context.py +91 -0
- pyfect/http/url.py +442 -0
- pyfect/http/url_params.py +304 -0
- pyfect/http/uvicorn_server.py +119 -0
- pyfect/http/web.py +171 -0
- pyfect/http/web_handler.py +463 -0
- pyfect/http/wire.py +209 -0
- pyfect/http_api/__init__.py +83 -0
- pyfect/http_api/_assets/scalar.js +8 -0
- pyfect/http_api/_assets/swagger-ui.css +3 -0
- pyfect/http_api/_assets/swagger-ui.js +4 -0
- pyfect/http_api/_client_method.py +220 -0
- pyfect/http_api/_client_payload.py +144 -0
- pyfect/http_api/_client_response.py +377 -0
- pyfect/http_api/_handlers.py +282 -0
- pyfect/http_api/_html.py +38 -0
- pyfect/http_api/_media_type.py +6 -0
- pyfect/http_api/_openapi_responses.py +164 -0
- pyfect/http_api/_openapi_schemas.py +163 -0
- pyfect/http_api/_path.py +102 -0
- pyfect/http_api/_payload.py +260 -0
- pyfect/http_api/_response.py +363 -0
- pyfect/http_api/_security.py +174 -0
- pyfect/http_api/api.py +265 -0
- pyfect/http_api/builder.py +447 -0
- pyfect/http_api/client.py +474 -0
- pyfect/http_api/endpoint.py +969 -0
- pyfect/http_api/error.py +280 -0
- pyfect/http_api/group.py +215 -0
- pyfect/http_api/middleware.py +232 -0
- pyfect/http_api/openapi.py +396 -0
- pyfect/http_api/scalar.py +143 -0
- pyfect/http_api/schema.py +713 -0
- pyfect/http_api/security.py +91 -0
- pyfect/http_api/swagger.py +69 -0
- pyfect/http_api/test.py +105 -0
- pyfect/json_patch.py +255 -0
- pyfect/json_pointer.py +100 -0
- pyfect/json_schema/__init__.py +66 -0
- pyfect/json_schema/_api.py +96 -0
- pyfect/json_schema/_converter.py +258 -0
- pyfect/json_schema/_draft04.py +198 -0
- pyfect/json_schema/_draft07.py +203 -0
- pyfect/json_schema/_keywords.py +202 -0
- pyfect/json_schema/_model.py +201 -0
- pyfect/json_schema/_openapi.py +208 -0
- pyfect/latch.py +127 -0
- pyfect/layer.py +479 -0
- pyfect/layer_map.py +279 -0
- pyfect/layer_ref.py +208 -0
- pyfect/logger/__init__.py +91 -0
- pyfect/logger/_batched.py +49 -0
- pyfect/logger/_console.py +116 -0
- pyfect/logger/_file.py +31 -0
- pyfect/logger/_formats.py +104 -0
- pyfect/logger/_layer.py +50 -0
- pyfect/logger/_level.py +53 -0
- pyfect/logger/_log.py +142 -0
- pyfect/logger/_model.py +163 -0
- pyfect/logger/_refs.py +78 -0
- pyfect/logger/_stdlib.py +54 -0
- pyfect/logger/_text.py +263 -0
- pyfect/logger/_tracer.py +35 -0
- pyfect/managed_runtime.py +393 -0
- pyfect/metric/__init__.py +119 -0
- pyfect/metric/_boundaries.py +27 -0
- pyfect/metric/_kinds.py +417 -0
- pyfect/metric/_metric.py +302 -0
- pyfect/metric/_prometheus.py +150 -0
- pyfect/metric/_refs.py +78 -0
- pyfect/metric/_runtime.py +75 -0
- pyfect/metric/_snapshot.py +97 -0
- pyfect/metric/_state.py +74 -0
- pyfect/msgspec.py +98 -0
- pyfect/mutable_hash_map.py +140 -0
- pyfect/mutable_hash_set.py +95 -0
- pyfect/net/__init__.py +18 -0
- pyfect/net/_errors.py +29 -0
- pyfect/net/address_resolver.py +179 -0
- pyfect/net/dns/__init__.py +92 -0
- pyfect/net/dns/_error.py +43 -0
- pyfect/net/dns/_records.py +335 -0
- pyfect/net/dns/_service.py +287 -0
- pyfect/net/dns/_system.py +89 -0
- pyfect/net/host.py +311 -0
- pyfect/net/ip_interface.py +202 -0
- pyfect/net/ip_network.py +250 -0
- pyfect/net/net_address/__init__.py +245 -0
- pyfect/net/net_address/_inet.py +412 -0
- pyfect/net/net_address/_ip.py +348 -0
- pyfect/net/net_address/_mac.py +119 -0
- pyfect/optic/__init__.py +50 -0
- pyfect/optic/_builtins.py +90 -0
- pyfect/optic/_data.py +97 -0
- pyfect/optic/_optic.py +422 -0
- pyfect/optic/_steps.py +151 -0
- pyfect/option.py +362 -0
- pyfect/otel/__init__.py +22 -0
- pyfect/otel/_attributes.py +67 -0
- pyfect/otel/_producer.py +322 -0
- pyfect/otel/_shutdown.py +26 -0
- pyfect/otel/_span.py +292 -0
- pyfect/otel/_wrapper.py +92 -0
- pyfect/otel/logger.py +154 -0
- pyfect/otel/metrics.py +126 -0
- pyfect/otel/resource.py +111 -0
- pyfect/otel/sdk.py +195 -0
- pyfect/otel/tracer.py +157 -0
- pyfect/partitioned_semaphore.py +211 -0
- pyfect/path.py +304 -0
- pyfect/persistence/__init__.py +77 -0
- pyfect/persistence/_columns.py +32 -0
- pyfect/persistence/_error.py +14 -0
- pyfect/persistence/_seconds.py +68 -0
- pyfect/persistence/_sql_keys.py +47 -0
- pyfect/persistence/circuit_breaker/__init__.py +120 -0
- pyfect/persistence/circuit_breaker/_breaker.py +317 -0
- pyfect/persistence/circuit_breaker/_errors.py +67 -0
- pyfect/persistence/circuit_breaker/_memory.py +243 -0
- pyfect/persistence/circuit_breaker/_redis.py +509 -0
- pyfect/persistence/circuit_breaker/_store.py +60 -0
- pyfect/persistence/circuit_breaker/_types.py +191 -0
- pyfect/persistence/key_value_store/__init__.py +51 -0
- pyfect/persistence/key_value_store/_layers.py +216 -0
- pyfect/persistence/key_value_store/_schema_store.py +91 -0
- pyfect/persistence/key_value_store/_sql.py +197 -0
- pyfect/persistence/key_value_store/_store.py +212 -0
- pyfect/persistence/persistable.py +164 -0
- pyfect/persistence/persisted_cache.py +159 -0
- pyfect/persistence/persisted_queue/__init__.py +75 -0
- pyfect/persistence/persisted_queue/_error.py +42 -0
- pyfect/persistence/persisted_queue/_mailbox.py +101 -0
- pyfect/persistence/persisted_queue/_memory.py +182 -0
- pyfect/persistence/persisted_queue/_queue.py +272 -0
- pyfect/persistence/persisted_queue/_redis.py +455 -0
- pyfect/persistence/persisted_queue/_redis_scripts.py +395 -0
- pyfect/persistence/persisted_queue/_sql.py +334 -0
- pyfect/persistence/persisted_queue/_sql_migrations.py +319 -0
- pyfect/persistence/persisted_queue/_sql_statements.py +441 -0
- pyfect/persistence/persisted_queue/_store.py +147 -0
- pyfect/persistence/persistence/__init__.py +84 -0
- pyfect/persistence/persistence/_kvs.py +135 -0
- pyfect/persistence/persistence/_memory.py +62 -0
- pyfect/persistence/persistence/_redis.py +162 -0
- pyfect/persistence/persistence/_service.py +276 -0
- pyfect/persistence/persistence/_sql.py +499 -0
- pyfect/persistence/rate_limiter/__init__.py +82 -0
- pyfect/persistence/rate_limiter/_errors.py +59 -0
- pyfect/persistence/rate_limiter/_limiter.py +325 -0
- pyfect/persistence/rate_limiter/_memory.py +217 -0
- pyfect/persistence/rate_limiter/_redis.py +485 -0
- pyfect/persistence/rate_limiter/_store.py +89 -0
- pyfect/persistence/redis/__init__.py +52 -0
- pyfect/persistence/redis/_error.py +12 -0
- pyfect/persistence/redis/_make.py +234 -0
- pyfect/persistence/redis/_script.py +78 -0
- pyfect/persistence/redis/_service.py +104 -0
- pyfect/persistence/redis_py.py +303 -0
- pyfect/persistence/user_directories.py +195 -0
- pyfect/platform.py +26 -0
- pyfect/platform_error.py +256 -0
- pyfect/pool/__init__.py +36 -0
- pyfect/pool/_pool.py +707 -0
- pyfect/pool/_state.py +161 -0
- pyfect/pool/_ttl.py +105 -0
- pyfect/pool/_waiters.py +116 -0
- pyfect/primary_key.py +35 -0
- pyfect/process/__init__.py +82 -0
- pyfect/process/_child.py +213 -0
- pyfect/process/_command.py +358 -0
- pyfect/process/_handle.py +77 -0
- pyfect/process/_live.py +455 -0
- pyfect/process/_os.py +78 -0
- pyfect/process/_pipes.py +291 -0
- pyfect/process/_spawner.py +75 -0
- pyfect/pubsub/__init__.py +33 -0
- pyfect/pubsub/_atomic.py +181 -0
- pyfect/pubsub/_hub.py +214 -0
- pyfect/pubsub/_pubsub.py +214 -0
- pyfect/pubsub/_replay.py +107 -0
- pyfect/pubsub/_subscription.py +226 -0
- pyfect/pull.py +132 -0
- pyfect/py.typed +0 -0
- pyfect/pydantic.py +188 -0
- pyfect/queue/__init__.py +33 -0
- pyfect/queue/_queue.py +525 -0
- pyfect/queue/_state.py +111 -0
- pyfect/queue/_views.py +55 -0
- pyfect/random.py +146 -0
- pyfect/rc_map.py +363 -0
- pyfect/rc_ref.py +271 -0
- pyfect/reactivity/__init__.py +53 -0
- pyfect/reactivity/_async_result_schema.py +227 -0
- pyfect/reactivity/_hydration.py +18 -0
- pyfect/reactivity/_live.py +21 -0
- pyfect/reactivity/_reactivity.py +256 -0
- pyfect/reactivity/_result_effect.py +17 -0
- pyfect/reactivity/_time.py +11 -0
- pyfect/reactivity/async_result.py +679 -0
- pyfect/reactivity/atom/__init__.py +143 -0
- pyfect/reactivity/atom/_combinators.py +223 -0
- pyfect/reactivity/atom/_constructors.py +80 -0
- pyfect/reactivity/atom/_conversions.py +79 -0
- pyfect/reactivity/atom/_effect.py +252 -0
- pyfect/reactivity/atom/_fn.py +341 -0
- pyfect/reactivity/atom/_kvs.py +101 -0
- pyfect/reactivity/atom/_optimistic.py +162 -0
- pyfect/reactivity/atom/_runtime.py +323 -0
- pyfect/reactivity/atom/_serializable.py +65 -0
- pyfect/reactivity/atom/_subscription_ref.py +102 -0
- pyfect/reactivity/atom/_swr.py +69 -0
- pyfect/reactivity/atom/_types.py +435 -0
- pyfect/reactivity/atom_http_api.py +667 -0
- pyfect/reactivity/atom_ref.py +260 -0
- pyfect/reactivity/atom_registry/__init__.py +219 -0
- pyfect/reactivity/atom_registry/_batch.py +96 -0
- pyfect/reactivity/atom_registry/_dispatcher.py +60 -0
- pyfect/reactivity/atom_registry/_lifetime.py +202 -0
- pyfect/reactivity/atom_registry/_node.py +423 -0
- pyfect/reactivity/atom_registry/_registry.py +380 -0
- pyfect/reactivity/atom_rpc.py +283 -0
- pyfect/reactivity/hydration.py +200 -0
- pyfect/redacted.py +83 -0
- pyfect/ref.py +129 -0
- pyfect/resource.py +98 -0
- pyfect/result.py +306 -0
- pyfect/rpc/__init__.py +64 -0
- pyfect/rpc/_encoded_schema.py +86 -0
- pyfect/rpc/_json_rpc.py +246 -0
- pyfect/rpc/_methods.py +177 -0
- pyfect/rpc/_rpc.py +498 -0
- pyfect/rpc/client/__init__.py +100 -0
- pyfect/rpc/client/_core.py +449 -0
- pyfect/rpc/client/_http.py +257 -0
- pyfect/rpc/client/_protocol.py +171 -0
- pyfect/rpc/client/_serialized.py +321 -0
- pyfect/rpc/client/_transports.py +328 -0
- pyfect/rpc/client/_worker.py +283 -0
- pyfect/rpc/client_error.py +35 -0
- pyfect/rpc/group.py +296 -0
- pyfect/rpc/message.py +299 -0
- pyfect/rpc/middleware.py +182 -0
- pyfect/rpc/schema.py +89 -0
- pyfect/rpc/serialization.py +366 -0
- pyfect/rpc/server/__init__.py +181 -0
- pyfect/rpc/server/_core.py +549 -0
- pyfect/rpc/server/_protocol.py +123 -0
- pyfect/rpc/server/_serialized.py +363 -0
- pyfect/rpc/server/_stdio.py +101 -0
- pyfect/rpc/server/_transports.py +427 -0
- pyfect/rpc/server/_worker.py +108 -0
- pyfect/rpc/test.py +74 -0
- pyfect/rpc/worker.py +92 -0
- pyfect/run.py +393 -0
- pyfect/schedule/__init__.py +91 -0
- pyfect/schedule/_combinators.py +168 -0
- pyfect/schedule/_constructors.py +223 -0
- pyfect/schedule/_cron.py +49 -0
- pyfect/schedule/_drive.py +191 -0
- pyfect/schedule/_model.py +405 -0
- pyfect/scheduler.py +575 -0
- pyfect/schema/__init__.py +866 -0
- pyfect/schema/_adapters.py +39 -0
- pyfect/schema/_arbitrary_constraint.py +242 -0
- pyfect/schema/_arbitrary_graph.py +325 -0
- pyfect/schema/_arbitrary_links.py +179 -0
- pyfect/schema/_arbitrary_number.py +112 -0
- pyfect/schema/_arrays.py +287 -0
- pyfect/schema/_assignable.py +118 -0
- pyfect/schema/_boolean_literals.py +31 -0
- pyfect/schema/_checks_number.py +216 -0
- pyfect/schema/_checks_order.py +140 -0
- pyfect/schema/_checks_size.py +277 -0
- pyfect/schema/_checks_string.py +241 -0
- pyfect/schema/_class.py +569 -0
- pyfect/schema/_codec_derivation.py +227 -0
- pyfect/schema/_codec_links.py +221 -0
- pyfect/schema/_codecs.py +316 -0
- pyfect/schema/_collections.py +172 -0
- pyfect/schema/_constructor_default.py +89 -0
- pyfect/schema/_data.py +440 -0
- pyfect/schema/_data_codecs.py +145 -0
- pyfect/schema/_decimal_regexp.py +243 -0
- pyfect/schema/_declare.py +68 -0
- pyfect/schema/_derive.py +292 -0
- pyfect/schema/_duration_codecs.py +27 -0
- pyfect/schema/_ecma_pattern.py +416 -0
- pyfect/schema/_error.py +26 -0
- pyfect/schema/_error_instance.py +222 -0
- pyfect/schema/_graph.py +271 -0
- pyfect/schema/_instances.py +240 -0
- pyfect/schema/_jsonschema/__init__.py +90 -0
- pyfect/schema/_jsonschema/_annotations.py +114 -0
- pyfect/schema/_jsonschema/_checks.py +253 -0
- pyfect/schema/_jsonschema/_compile.py +346 -0
- pyfect/schema/_jsonschema/_exact.py +19 -0
- pyfect/schema/_jsonschema/_merge.py +211 -0
- pyfect/schema/_jsonschema/_objects.py +231 -0
- pyfect/schema/_jsonschema/_references.py +221 -0
- pyfect/schema/_jsonschema/_template.py +69 -0
- pyfect/schema/_literals.py +147 -0
- pyfect/schema/_middleware.py +209 -0
- pyfect/schema/_net.py +522 -0
- pyfect/schema/_options.py +49 -0
- pyfect/schema/_parse.py +351 -0
- pyfect/schema/_parsed_text.py +37 -0
- pyfect/schema/_primitives.py +48 -0
- pyfect/schema/_record.py +41 -0
- pyfect/schema/_refine.py +81 -0
- pyfect/schema/_schema.py +158 -0
- pyfect/schema/_semver.py +144 -0
- pyfect/schema/_strategy/__init__.py +258 -0
- pyfect/schema/_strategy/_big_decimal.py +203 -0
- pyfect/schema/_strategy/_composites.py +119 -0
- pyfect/schema/_strategy/_leaves.py +94 -0
- pyfect/schema/_strategy/_net.py +133 -0
- pyfect/schema/_struct.py +87 -0
- pyfect/schema/_struct_keys.py +128 -0
- pyfect/schema/_tagged.py +254 -0
- pyfect/schema/_template_literal.py +172 -0
- pyfect/schema/_to_codec_array_from_single.py +115 -0
- pyfect/schema/_to_codec_go_json.py +174 -0
- pyfect/schema/_to_codec_json.py +73 -0
- pyfect/schema/_to_codec_string_tree.py +115 -0
- pyfect/schema/_to_differ_json_patch.py +44 -0
- pyfect/schema/_to_encoder_xml.py +119 -0
- pyfect/schema/_to_equivalence.py +186 -0
- pyfect/schema/_to_formatter.py +180 -0
- pyfect/schema/_to_iso.py +73 -0
- pyfect/schema/_to_strategy.py +31 -0
- pyfect/schema/_transform.py +210 -0
- pyfect/schema/_tree.py +27 -0
- pyfect/schema/_type_walk.py +53 -0
- pyfect/schema/_undefined.py +58 -0
- pyfect/schema/_value_codecs.py +84 -0
- pyfect/schema/_values.py +123 -0
- pyfect/schema/_void.py +86 -0
- pyfect/schema/ast/__init__.py +227 -0
- pyfect/schema/ast/_annotations.py +43 -0
- pyfect/schema/ast/_arrays.py +210 -0
- pyfect/schema/ast/_base.py +295 -0
- pyfect/schema/ast/_candidates.py +309 -0
- pyfect/schema/ast/_check.py +181 -0
- pyfect/schema/ast/_class.py +159 -0
- pyfect/schema/ast/_constructors.py +65 -0
- pyfect/schema/ast/_declaration.py +97 -0
- pyfect/schema/ast/_encoding.py +111 -0
- pyfect/schema/ast/_json.py +129 -0
- pyfect/schema/ast/_keywords.py +125 -0
- pyfect/schema/ast/_literal.py +157 -0
- pyfect/schema/ast/_objects.py +356 -0
- pyfect/schema/ast/_record.py +36 -0
- pyfect/schema/ast/_result.py +116 -0
- pyfect/schema/ast/_steps.py +209 -0
- pyfect/schema/ast/_suspend.py +65 -0
- pyfect/schema/ast/_template_literal.py +256 -0
- pyfect/schema/ast/_union.py +172 -0
- pyfect/schema/getter/__init__.py +136 -0
- pyfect/schema/getter/_binary.py +104 -0
- pyfect/schema/getter/_coerce.py +196 -0
- pyfect/schema/getter/_getter.py +243 -0
- pyfect/schema/getter/_make.py +151 -0
- pyfect/schema/getter/_text.py +141 -0
- pyfect/schema/getter/_tree_record.py +170 -0
- pyfect/schema/issue.py +420 -0
- pyfect/schema/model/__init__.py +120 -0
- pyfect/schema/model/_class.py +245 -0
- pyfect/schema/model/_dates.py +103 -0
- pyfect/schema/model/_fields.py +77 -0
- pyfect/schema/model/_toolkit.py +114 -0
- pyfect/schema/model/_uuid.py +69 -0
- pyfect/schema/representation/__init__.py +424 -0
- pyfect/schema/representation/_builtin_revivers.py +331 -0
- pyfect/schema/representation/_code.py +106 -0
- pyfect/schema/representation/_code_builtins.py +281 -0
- pyfect/schema/representation/_code_document.py +563 -0
- pyfect/schema/representation/_code_sort.py +111 -0
- pyfect/schema/representation/_from.py +278 -0
- pyfect/schema/representation/_import/__init__.py +78 -0
- pyfect/schema/representation/_import/_finalize.py +121 -0
- pyfect/schema/representation/_import/_intersect.py +467 -0
- pyfect/schema/representation/_import/_js_order.py +66 -0
- pyfect/schema/representation/_import/_keywords.py +224 -0
- pyfect/schema/representation/_import/_nodes.py +199 -0
- pyfect/schema/representation/_import/_translate.py +455 -0
- pyfect/schema/representation/_json.py +520 -0
- pyfect/schema/representation/_json_schema.py +178 -0
- pyfect/schema/representation/_model.py +284 -0
- pyfect/schema/representation/_revivers.py +89 -0
- pyfect/schema/representation/_to.py +249 -0
- pyfect/schema/transformation/__init__.py +99 -0
- pyfect/schema/transformation/_builtins.py +314 -0
- pyfect/schema/transformation/_durations.py +150 -0
- pyfect/schema/transformation/_transformation.py +177 -0
- pyfect/schema/variant_schema/__init__.py +69 -0
- pyfect/schema/variant_schema/_class.py +188 -0
- pyfect/schema/variant_schema/_defaults.py +30 -0
- pyfect/schema/variant_schema/_field.py +32 -0
- pyfect/schema/variant_schema/_struct.py +130 -0
- pyfect/schema/variant_schema/_toolkit.py +160 -0
- pyfect/scope.py +374 -0
- pyfect/scoped_ref.py +124 -0
- pyfect/semaphore.py +276 -0
- pyfect/semver.py +403 -0
- pyfect/semver_range.py +420 -0
- pyfect/sink/__init__.py +131 -0
- pyfect/sink/_combinators.py +151 -0
- pyfect/sink/_constructors.py +187 -0
- pyfect/sink/_context.py +87 -0
- pyfect/sink/_folds.py +336 -0
- pyfect/sink/_for_each.py +87 -0
- pyfect/sink/_sink.py +265 -0
- pyfect/sink/_steps.py +34 -0
- pyfect/sink/_taking.py +183 -0
- pyfect/socket/__init__.py +146 -0
- pyfect/socket/_accepted.py +65 -0
- pyfect/socket/_connection.py +384 -0
- pyfect/socket/_errors.py +135 -0
- pyfect/socket/_loop.py +66 -0
- pyfect/socket/_net.py +281 -0
- pyfect/socket/_socket.py +245 -0
- pyfect/socket/_tls.py +88 -0
- pyfect/socket/_tls_session.py +117 -0
- pyfect/socket/_web_socket.py +555 -0
- pyfect/socket/datagram/__init__.py +110 -0
- pyfect/socket/datagram/_core.py +460 -0
- pyfect/socket/datagram/_errors.py +102 -0
- pyfect/socket/datagram/_live.py +675 -0
- pyfect/socket/datagram/_model.py +217 -0
- pyfect/socket/server.py +258 -0
- pyfect/socket/ws.py +484 -0
- pyfect/sql/__init__.py +60 -0
- pyfect/sql/_loop.py +134 -0
- pyfect/sql/clickhouse/__init__.py +74 -0
- pyfect/sql/clickhouse/_connection.py +237 -0
- pyfect/sql/clickhouse/_rows.py +87 -0
- pyfect/sql/clickhouse/client.py +322 -0
- pyfect/sql/clickhouse/config.py +241 -0
- pyfect/sql/clickhouse/driver.py +434 -0
- pyfect/sql/clickhouse/error.py +106 -0
- pyfect/sql/clickhouse/migrator.py +47 -0
- pyfect/sql/clickhouse/params.py +168 -0
- pyfect/sql/client/__init__.py +35 -0
- pyfect/sql/client/_client.py +309 -0
- pyfect/sql/client/_sqlite_acquirers.py +130 -0
- pyfect/sql/client/_transaction.py +410 -0
- pyfect/sql/connection.py +87 -0
- pyfect/sql/d1/__init__.py +48 -0
- pyfect/sql/d1/api.py +270 -0
- pyfect/sql/d1/client.py +344 -0
- pyfect/sql/d1/config.py +55 -0
- pyfect/sql/error.py +311 -0
- pyfect/sql/libsql/__init__.py +58 -0
- pyfect/sql/libsql/_connection.py +110 -0
- pyfect/sql/libsql/_hrana.py +258 -0
- pyfect/sql/libsql/_local.py +251 -0
- pyfect/sql/libsql/_targets.py +28 -0
- pyfect/sql/libsql/_values.py +46 -0
- pyfect/sql/libsql/client.py +225 -0
- pyfect/sql/libsql/config.py +170 -0
- pyfect/sql/libsql/migrator.py +43 -0
- pyfect/sql/libsql/sdk.py +123 -0
- pyfect/sql/migrator.py +434 -0
- pyfect/sql/model.py +324 -0
- pyfect/sql/mssql/__init__.py +79 -0
- pyfect/sql/mssql/_connection.py +169 -0
- pyfect/sql/mssql/_driver.py +103 -0
- pyfect/sql/mssql/_names.py +9 -0
- pyfect/sql/mssql/_pool.py +125 -0
- pyfect/sql/mssql/_tds.py +447 -0
- pyfect/sql/mssql/_tds_params.py +155 -0
- pyfect/sql/mssql/_worker.py +155 -0
- pyfect/sql/mssql/client.py +344 -0
- pyfect/sql/mssql/config.py +118 -0
- pyfect/sql/mssql/error.py +111 -0
- pyfect/sql/mssql/migrator.py +43 -0
- pyfect/sql/mssql/parameter.py +44 -0
- pyfect/sql/mssql/procedure.py +119 -0
- pyfect/sql/mssql/types.py +137 -0
- pyfect/sql/mysql/__init__.py +60 -0
- pyfect/sql/mysql/_asyncmy.py +168 -0
- pyfect/sql/mysql/_bind.py +76 -0
- pyfect/sql/mysql/_connection.py +156 -0
- pyfect/sql/mysql/_pool.py +63 -0
- pyfect/sql/mysql/_session.py +262 -0
- pyfect/sql/mysql/client.py +244 -0
- pyfect/sql/mysql/config.py +108 -0
- pyfect/sql/mysql/error.py +104 -0
- pyfect/sql/mysql/migrator.py +44 -0
- pyfect/sql/pg/__init__.py +82 -0
- pyfect/sql/pg/_resolve.py +284 -0
- pyfect/sql/pg/_session.py +380 -0
- pyfect/sql/pg/client.py +398 -0
- pyfect/sql/pg/config.py +142 -0
- pyfect/sql/pg/connection.py +585 -0
- pyfect/sql/pg/error.py +131 -0
- pyfect/sql/pg/migrator.py +139 -0
- pyfect/sql/pg/pool.py +265 -0
- pyfect/sql/resolver.py +294 -0
- pyfect/sql/result.py +24 -0
- pyfect/sql/schema.py +116 -0
- pyfect/sql/sqlite/__init__.py +26 -0
- pyfect/sql/sqlite/_connection.py +230 -0
- pyfect/sql/sqlite/_worker.py +71 -0
- pyfect/sql/sqlite/client.py +248 -0
- pyfect/sql/sqlite/migrator.py +43 -0
- pyfect/sql/statement/__init__.py +144 -0
- pyfect/sql/statement/_compiler.py +384 -0
- pyfect/sql/statement/_segments.py +487 -0
- pyfect/sql/statement/_statement.py +526 -0
- pyfect/sql/statement/_transforms.py +93 -0
- pyfect/sql/stream.py +88 -0
- pyfect/stdio.py +206 -0
- pyfect/stream/__init__.py +151 -0
- pyfect/stream/_async_iterable.py +129 -0
- pyfect/stream/_base.py +69 -0
- pyfect/stream/_bridge_methods.py +139 -0
- pyfect/stream/_catch.py +135 -0
- pyfect/stream/_catch_reason.py +558 -0
- pyfect/stream/_channel_methods.py +61 -0
- pyfect/stream/_chunk_methods.py +169 -0
- pyfect/stream/_chunking.py +310 -0
- pyfect/stream/_concat.py +65 -0
- pyfect/stream/_constructors.py +341 -0
- pyfect/stream/_context.py +147 -0
- pyfect/stream/_error_methods.py +254 -0
- pyfect/stream/_execution_plan.py +199 -0
- pyfect/stream/_fanout.py +154 -0
- pyfect/stream/_fanout_methods.py +183 -0
- pyfect/stream/_filter_methods.py +203 -0
- pyfect/stream/_grouping.py +168 -0
- pyfect/stream/_many.py +65 -0
- pyfect/stream/_map_methods.py +233 -0
- pyfect/stream/_mapping.py +126 -0
- pyfect/stream/_run.py +114 -0
- pyfect/stream/_run_methods.py +114 -0
- pyfect/stream/_service_methods.py +36 -0
- pyfect/stream/_sink_methods.py +151 -0
- pyfect/stream/_sinking.py +277 -0
- pyfect/stream/_sources.py +124 -0
- pyfect/stream/_stream.py +162 -0
- pyfect/stream/_taking.py +330 -0
- pyfect/stream/_text.py +43 -0
- pyfect/stream/_timing.py +294 -0
- pyfect/stream/_timing_methods.py +252 -0
- pyfect/stream/_zip_methods.py +279 -0
- pyfect/stream/_zipping.py +213 -0
- pyfect/subscription_ref.py +66 -0
- pyfect/synchronized_ref.py +38 -0
- pyfect/take.py +36 -0
- pyfect/terminal/__init__.py +41 -0
- pyfect/terminal/_keys.py +113 -0
- pyfect/terminal/_live.py +223 -0
- pyfect/terminal/_service.py +73 -0
- pyfect/testing/__init__.py +122 -0
- pyfect/testing/_assertions.py +92 -0
- pyfect/testing/_clock.py +309 -0
- pyfect/testing/_console.py +164 -0
- pyfect/testing/_crypto.py +44 -0
- pyfect/testing/_decorators.py +211 -0
- pyfect/testing/_flaky.py +34 -0
- pyfect/testing/_layers.py +153 -0
- pyfect/testing/_prop.py +255 -0
- pyfect/testing/_runners.py +84 -0
- pyfect/testing/_runtime.py +174 -0
- pyfect/testing/_schema.py +238 -0
- pyfect/testing/plugin.py +59 -0
- pyfect/tracer/__init__.py +107 -0
- pyfect/tracer/_model.py +176 -0
- pyfect/tracer/_native.py +172 -0
- pyfect/tracer/_refs.py +87 -0
- pyfect/tracer/_spans.py +269 -0
- pyfect/transaction.py +340 -0
- pyfect/trie.py +317 -0
- pyfect/tx_chunk.py +127 -0
- pyfect/tx_deferred.py +75 -0
- pyfect/tx_hash_map.py +265 -0
- pyfect/tx_hash_set.py +151 -0
- pyfect/tx_priority_queue.py +224 -0
- pyfect/tx_pubsub.py +194 -0
- pyfect/tx_queue/__init__.py +27 -0
- pyfect/tx_queue/_queue.py +359 -0
- pyfect/tx_queue/_views.py +60 -0
- pyfect/tx_reentrant_lock.py +201 -0
- pyfect/tx_ref.py +79 -0
- pyfect/tx_semaphore.py +136 -0
- pyfect/tx_subscription_ref.py +118 -0
- pyfect/workers/__init__.py +62 -0
- pyfect/workers/_frames.py +82 -0
- pyfect/workers/_mailbox.py +51 -0
- pyfect/workers/_port.py +106 -0
- pyfect/workers/_process.py +155 -0
- pyfect/workers/_thread.py +100 -0
- pyfect/workers/python_worker.py +133 -0
- pyfect/workers/python_worker_runner.py +112 -0
- pyfect/workers/transferable.py +103 -0
- pyfect/workers/worker.py +273 -0
- pyfect/workers/worker_error.py +86 -0
- pyfect/workers/worker_runner.py +85 -0
- pyfect/workflow/__init__.py +78 -0
- pyfect/workflow/_crypto.py +23 -0
- pyfect/workflow/_encoded.py +381 -0
- pyfect/workflow/_memory.py +424 -0
- pyfect/workflow/_result.py +43 -0
- pyfect/workflow/_services.py +241 -0
- pyfect/workflow/_suspension.py +153 -0
- pyfect/workflow/activity.py +427 -0
- pyfect/workflow/durable_clock.py +72 -0
- pyfect/workflow/durable_deferred.py +403 -0
- pyfect/workflow/durable_queue.py +259 -0
- pyfect/workflow/workflow.py +509 -0
- pyfect/workflow/workflow_engine.py +42 -0
- pyfect/workflow/workflow_proxy.py +161 -0
- pyfect/workflow/workflow_proxy_server.py +146 -0
- pyfect_dev-0.1.0.dist-info/METADATA +296 -0
- pyfect_dev-0.1.0.dist-info/RECORD +1265 -0
- pyfect_dev-0.1.0.dist-info/WHEEL +4 -0
- pyfect_dev-0.1.0.dist-info/entry_points.txt +3 -0
- pyfect_dev-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""The DNS service (Effect's ``Dns``): built from a lookup, and optionally a
|
|
2
|
+
record resolver and reverse lookup, by ``make``; or from a fixed zone by
|
|
3
|
+
``make_static``."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from collections.abc import Callable, Iterable, Mapping, Sequence
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from ipaddress import IPv4Address, IPv6Address
|
|
11
|
+
from typing import Literal, overload
|
|
12
|
+
|
|
13
|
+
from pyfect._core import Effect
|
|
14
|
+
from pyfect.context import Tag
|
|
15
|
+
from pyfect.effect import fail, from_result, succeed, sync
|
|
16
|
+
from pyfect.layer import Layer
|
|
17
|
+
from pyfect.layer import effect as layer_effect
|
|
18
|
+
from pyfect.net._errors import NetAddressError
|
|
19
|
+
from pyfect.net.dns._error import DnsError, DnsMethod
|
|
20
|
+
from pyfect.net.dns._records import (
|
|
21
|
+
A,
|
|
22
|
+
Aaaa,
|
|
23
|
+
DnsRecord,
|
|
24
|
+
Ptr,
|
|
25
|
+
RecordType,
|
|
26
|
+
record_classes,
|
|
27
|
+
reverse_name,
|
|
28
|
+
)
|
|
29
|
+
from pyfect.net.host import DomainName, DomainNameInput, domain_name_from_input, to_relative
|
|
30
|
+
from pyfect.net.net_address._inet import (
|
|
31
|
+
InetAddress,
|
|
32
|
+
InetAddressInput,
|
|
33
|
+
InetAddressV4,
|
|
34
|
+
InetAddressV6,
|
|
35
|
+
inet_address_from_input,
|
|
36
|
+
inet_address_unsafe,
|
|
37
|
+
)
|
|
38
|
+
from pyfect.net.net_address._inet import inet_address_from_string as parse_inet
|
|
39
|
+
from pyfect.net.net_address._ip import (
|
|
40
|
+
IpAddress,
|
|
41
|
+
IpAddressInput,
|
|
42
|
+
IpFamily,
|
|
43
|
+
family_of,
|
|
44
|
+
format_ip,
|
|
45
|
+
ip_from_input,
|
|
46
|
+
)
|
|
47
|
+
from pyfect.result import Err, Ok, Result
|
|
48
|
+
|
|
49
|
+
type Lookup = Callable[[DomainName, IpFamily | None], Effect[Sequence[IpAddress], DnsError]]
|
|
50
|
+
type Resolve = Callable[[DomainName, RecordType], Effect[Sequence[DnsRecord], DnsError]]
|
|
51
|
+
type Reverse = Callable[[IpAddress], Effect[Sequence[str], DnsError]]
|
|
52
|
+
|
|
53
|
+
_ASCII_NAME = re.compile(r"[\w.-]+", re.ASCII)
|
|
54
|
+
_HOST_PORT = re.compile(r"[^:]*:[0-9]+")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _dedupe[A](values: Iterable[A]) -> tuple[A, ...]:
|
|
58
|
+
return tuple(dict.fromkeys(values))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _not_found(method: DnsMethod, hostname: str, record_type: RecordType | None = None) -> DnsError:
|
|
62
|
+
return DnsError("NotFound", method, hostname, record_type)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True, slots=True)
|
|
66
|
+
class DnsService:
|
|
67
|
+
"""Host lookups, record queries and reverse lookups. Every result is
|
|
68
|
+
non-empty and without duplicates; an empty answer fails with
|
|
69
|
+
``NotFound``, a name that does not parse with ``BadName``."""
|
|
70
|
+
|
|
71
|
+
_lookup: Lookup
|
|
72
|
+
_resolve: Resolve
|
|
73
|
+
_reverse: Reverse
|
|
74
|
+
|
|
75
|
+
@overload
|
|
76
|
+
def lookup(
|
|
77
|
+
self, host: DomainNameInput, *, family: Literal["IPv4"]
|
|
78
|
+
) -> Effect[tuple[IPv4Address, ...], DnsError]: ...
|
|
79
|
+
@overload
|
|
80
|
+
def lookup(
|
|
81
|
+
self, host: DomainNameInput, *, family: Literal["IPv6"]
|
|
82
|
+
) -> Effect[tuple[IPv6Address, ...], DnsError]: ...
|
|
83
|
+
@overload
|
|
84
|
+
def lookup(
|
|
85
|
+
self, host: DomainNameInput, *, family: IpFamily | None = None
|
|
86
|
+
) -> Effect[tuple[IpAddress, ...], DnsError]: ...
|
|
87
|
+
def lookup(
|
|
88
|
+
self, host: DomainNameInput, *, family: IpFamily | None = None
|
|
89
|
+
) -> Effect[tuple[IpAddress, ...], DnsError]:
|
|
90
|
+
"""The addresses of ``host``, of ``family`` when given."""
|
|
91
|
+
match domain_name_from_input(host):
|
|
92
|
+
case Err(error):
|
|
93
|
+
return fail(DnsError("BadName", "lookup", host, cause=error))
|
|
94
|
+
case Ok(name):
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
def found(addresses: Sequence[IpAddress]) -> Effect[tuple[IpAddress, ...], DnsError]:
|
|
98
|
+
kept = _dedupe(a for a in addresses if family is None or family_of(a) == family)
|
|
99
|
+
return succeed(kept) if kept else fail(_not_found("lookup", name))
|
|
100
|
+
|
|
101
|
+
return self._lookup(name, family).flat_map(found)
|
|
102
|
+
|
|
103
|
+
def resolve[R: DnsRecord](
|
|
104
|
+
self, name: DomainNameInput, record_type: type[R]
|
|
105
|
+
) -> Effect[tuple[R, ...], DnsError]:
|
|
106
|
+
"""The records of ``record_type`` (a record class: ``dns.Mx``) at
|
|
107
|
+
``name``."""
|
|
108
|
+
tag = record_type.tag
|
|
109
|
+
match domain_name_from_input(name):
|
|
110
|
+
case Err(error):
|
|
111
|
+
return fail(DnsError("BadName", "resolve", name, tag, error))
|
|
112
|
+
case Ok(domain):
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
def found(records: Sequence[DnsRecord]) -> Effect[tuple[R, ...], DnsError]:
|
|
116
|
+
kept = _dedupe(r for r in records if isinstance(r, record_type))
|
|
117
|
+
return succeed(kept) if kept else fail(_not_found("resolve", domain, tag))
|
|
118
|
+
|
|
119
|
+
return self._resolve(domain, tag).flat_map(found)
|
|
120
|
+
|
|
121
|
+
def reverse(self, address: IpAddressInput) -> Effect[tuple[DomainName, ...], DnsError]:
|
|
122
|
+
"""The names of ``address``; only ASCII names are kept (a UTF-8 name
|
|
123
|
+
would become a different ``xn--`` name)."""
|
|
124
|
+
match ip_from_input(address):
|
|
125
|
+
case Err(error):
|
|
126
|
+
return fail(DnsError("BadName", "reverse", str(address), cause=error))
|
|
127
|
+
case Ok(ip):
|
|
128
|
+
pass
|
|
129
|
+
hostname = format_ip(ip)
|
|
130
|
+
|
|
131
|
+
def found(names: Sequence[str]) -> Effect[tuple[DomainName, ...], DnsError]:
|
|
132
|
+
hosts = _dedupe(
|
|
133
|
+
parsed.value
|
|
134
|
+
for name in names
|
|
135
|
+
if _ASCII_NAME.fullmatch(name)
|
|
136
|
+
for parsed in (domain_name_from_input(name),)
|
|
137
|
+
if isinstance(parsed, Ok)
|
|
138
|
+
)
|
|
139
|
+
if hosts:
|
|
140
|
+
return succeed(hosts)
|
|
141
|
+
if names:
|
|
142
|
+
return fail(DnsError("InvalidResponse", "reverse", hostname))
|
|
143
|
+
return fail(_not_found("reverse", hostname))
|
|
144
|
+
|
|
145
|
+
return self._reverse(ip).flat_map(found)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class Dns(Tag[DnsService], key="effect/net/Dns"):
|
|
149
|
+
"""The DNS service."""
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def make(
|
|
153
|
+
lookup: Lookup, resolve: Resolve | None = None, reverse: Reverse | None = None
|
|
154
|
+
) -> DnsService:
|
|
155
|
+
"""A DNS service from its primitives. Without ``resolve``, record
|
|
156
|
+
queries fail with ``Unsupported``; without ``reverse``, reverse lookups
|
|
157
|
+
query PTR records."""
|
|
158
|
+
|
|
159
|
+
def unsupported(
|
|
160
|
+
name: DomainName, record_type: RecordType
|
|
161
|
+
) -> Effect[Sequence[DnsRecord], DnsError]:
|
|
162
|
+
return fail(DnsError("Unsupported", "resolve", name, record_type))
|
|
163
|
+
|
|
164
|
+
resolve_records = resolve or unsupported
|
|
165
|
+
|
|
166
|
+
def reverse_by_ptr(address: IpAddress) -> Effect[Sequence[str], DnsError]:
|
|
167
|
+
def rename(error: DnsError) -> DnsError:
|
|
168
|
+
return DnsError(error.reason, "reverse", format_ip(address), cause=error.cause)
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
resolve_records(reverse_name(address), "PTR")
|
|
172
|
+
.map(lambda records: [r.host for r in records if isinstance(r, Ptr)])
|
|
173
|
+
.map_error(rename)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return DnsService(lookup, resolve_records, reverse or reverse_by_ptr)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _zone_key(name: str) -> str:
|
|
180
|
+
return to_relative(DomainName(name))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@dataclass(slots=True)
|
|
184
|
+
class _Entry:
|
|
185
|
+
addresses: list[IpAddress]
|
|
186
|
+
records: list[DnsRecord]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def make_static(
|
|
190
|
+
hosts: Mapping[str, Sequence[IpAddressInput]] | None = None,
|
|
191
|
+
records: Mapping[str, Sequence[DnsRecord]] | None = None,
|
|
192
|
+
) -> Result[DnsService, NetAddressError]:
|
|
193
|
+
"""A DNS service answering from a fixed zone: addresses by host name,
|
|
194
|
+
records by name."""
|
|
195
|
+
entries: dict[str, _Entry] = {}
|
|
196
|
+
|
|
197
|
+
def entry(name: str) -> Result[_Entry, NetAddressError]:
|
|
198
|
+
return domain_name_from_input(name).map(
|
|
199
|
+
lambda domain: entries.setdefault(_zone_key(domain), _Entry([], []))
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
for name, inputs in (hosts or {}).items():
|
|
203
|
+
match entry(name):
|
|
204
|
+
case Err() as error:
|
|
205
|
+
return error
|
|
206
|
+
case Ok(current):
|
|
207
|
+
for value in inputs:
|
|
208
|
+
match ip_from_input(value):
|
|
209
|
+
case Err() as error:
|
|
210
|
+
return error
|
|
211
|
+
case Ok(address):
|
|
212
|
+
current.addresses.append(address)
|
|
213
|
+
for name, values in (records or {}).items():
|
|
214
|
+
match entry(name):
|
|
215
|
+
case Err() as error:
|
|
216
|
+
return error
|
|
217
|
+
case Ok(current):
|
|
218
|
+
for record in values:
|
|
219
|
+
if not isinstance(record, record_classes):
|
|
220
|
+
return Err(NetAddressError("expected a DNS record", record))
|
|
221
|
+
current.records.append(record)
|
|
222
|
+
|
|
223
|
+
def records_at(name: str) -> list[DnsRecord]:
|
|
224
|
+
found = entries.get(_zone_key(name))
|
|
225
|
+
return [] if found is None else found.records
|
|
226
|
+
|
|
227
|
+
def lookup(host: DomainName, _family: IpFamily | None) -> Effect[Sequence[IpAddress], DnsError]:
|
|
228
|
+
def answer() -> Sequence[IpAddress]:
|
|
229
|
+
found = entries.get(_zone_key(host))
|
|
230
|
+
direct = [] if found is None else found.addresses
|
|
231
|
+
return [*direct, *(r.address for r in records_at(host) if isinstance(r, A | Aaaa))]
|
|
232
|
+
|
|
233
|
+
return sync(answer)
|
|
234
|
+
|
|
235
|
+
def resolve(name: DomainName, _type: RecordType) -> Effect[Sequence[DnsRecord], DnsError]:
|
|
236
|
+
return sync(lambda: records_at(name))
|
|
237
|
+
|
|
238
|
+
def reverse(address: IpAddress) -> Effect[Sequence[str], DnsError]:
|
|
239
|
+
def answer() -> Sequence[str]:
|
|
240
|
+
named = [name for name, current in entries.items() if address in current.addresses]
|
|
241
|
+
pointed = [r.host for r in records_at(reverse_name(address)) if isinstance(r, Ptr)]
|
|
242
|
+
return [*named, *pointed]
|
|
243
|
+
|
|
244
|
+
return sync(answer)
|
|
245
|
+
|
|
246
|
+
return Ok(make(lookup, resolve, reverse))
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def layer_static(
|
|
250
|
+
hosts: Mapping[str, Sequence[IpAddressInput]] | None = None,
|
|
251
|
+
records: Mapping[str, Sequence[DnsRecord]] | None = None,
|
|
252
|
+
) -> Layer[Dns, NetAddressError]:
|
|
253
|
+
"""A ``Dns`` layer answering from a fixed zone."""
|
|
254
|
+
return layer_effect(Dns, from_result(make_static(hosts, records)))
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def name_server_from_string(text: str) -> Result[InetAddress, NetAddressError]:
|
|
258
|
+
"""A name server address; port 53 unless given."""
|
|
259
|
+
if text.startswith("[") or _HOST_PORT.fullmatch(text):
|
|
260
|
+
return parse_inet(text)
|
|
261
|
+
return parse_inet(f"[{text}]:53" if ":" in text else f"{text}:53")
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def name_server_from_input(
|
|
265
|
+
value: IpAddressInput | InetAddressInput,
|
|
266
|
+
) -> Result[InetAddress, NetAddressError]:
|
|
267
|
+
"""A name server address from text, parts with a port, or an IP address
|
|
268
|
+
(port 53)."""
|
|
269
|
+
if isinstance(value, str):
|
|
270
|
+
return name_server_from_string(value)
|
|
271
|
+
if isinstance(value, Mapping | InetAddressV4 | InetAddressV6):
|
|
272
|
+
return inet_address_from_input(value)
|
|
273
|
+
return ip_from_input(value).map(lambda address: inet_address_unsafe(address, 53))
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
__all__ = [
|
|
277
|
+
"Dns",
|
|
278
|
+
"DnsService",
|
|
279
|
+
"Lookup",
|
|
280
|
+
"Resolve",
|
|
281
|
+
"Reverse",
|
|
282
|
+
"layer_static",
|
|
283
|
+
"make",
|
|
284
|
+
"make_static",
|
|
285
|
+
"name_server_from_input",
|
|
286
|
+
"name_server_from_string",
|
|
287
|
+
]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""The operating system's resolver as the ``Dns`` service (Effect's
|
|
2
|
+
``NodeDns``, minus record queries).
|
|
3
|
+
|
|
4
|
+
Lookups go through ``getaddrinfo`` (the hosts file and the system's DNS
|
|
5
|
+
configuration, as Node's ``dns.lookup``); reverse lookups through
|
|
6
|
+
``gethostbyaddr``. Python's standard library has no DNS record queries, so
|
|
7
|
+
``resolve`` fails with ``Unsupported``. Each call runs on a thread of its
|
|
8
|
+
own and resumes the fiber through its scheduler; an interrupted call's
|
|
9
|
+
answer is dropped.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import socket
|
|
15
|
+
import threading
|
|
16
|
+
from collections.abc import Callable, Sequence
|
|
17
|
+
from ipaddress import ip_address
|
|
18
|
+
|
|
19
|
+
from pyfect._core import Effect
|
|
20
|
+
from pyfect._os_wait import Resume, os_wait
|
|
21
|
+
from pyfect.effect import fail, succeed
|
|
22
|
+
from pyfect.layer import Layer
|
|
23
|
+
from pyfect.layer import succeed as layer_succeed
|
|
24
|
+
from pyfect.net.dns._error import DnsError, DnsErrorReason, DnsMethod
|
|
25
|
+
from pyfect.net.dns._service import Dns, DnsService, make
|
|
26
|
+
from pyfect.net.host import DomainName
|
|
27
|
+
from pyfect.net.net_address._ip import IpAddress, IpFamily, format_ip
|
|
28
|
+
|
|
29
|
+
_REASON_NAMES: list[tuple[str, DnsErrorReason]] = [
|
|
30
|
+
("EAI_NONAME", "NotFound"),
|
|
31
|
+
("EAI_NODATA", "NotFound"),
|
|
32
|
+
("EAI_AGAIN", "Temporary"),
|
|
33
|
+
("EAI_FAIL", "Refused"),
|
|
34
|
+
("EAI_FAMILY", "BadName"),
|
|
35
|
+
]
|
|
36
|
+
_REASONS: dict[int, DnsErrorReason] = {
|
|
37
|
+
code: reason
|
|
38
|
+
for name, reason in _REASON_NAMES
|
|
39
|
+
if isinstance(code := getattr(socket, name, None), int)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_FAMILIES = {None: socket.AF_UNSPEC, "IPv4": socket.AF_INET, "IPv6": socket.AF_INET6}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def dns_error_from_cause(cause: OSError, method: DnsMethod, hostname: str) -> DnsError:
|
|
46
|
+
"""The ``DnsError`` of a resolver failure, by its error code."""
|
|
47
|
+
if isinstance(cause, socket.herror):
|
|
48
|
+
return DnsError("NotFound", method, hostname, cause=cause)
|
|
49
|
+
reason = _REASONS.get(cause.errno, "Unknown") if cause.errno is not None else "Unknown"
|
|
50
|
+
return DnsError(reason, method, hostname, cause=cause)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _on_thread[A](method: DnsMethod, hostname: str, call: Callable[[], A]) -> Effect[A, DnsError]:
|
|
54
|
+
def register(resume: Resume[A, DnsError]) -> None:
|
|
55
|
+
def run() -> None:
|
|
56
|
+
try:
|
|
57
|
+
resume(succeed(call()))
|
|
58
|
+
except OSError as cause:
|
|
59
|
+
resume(fail(dns_error_from_cause(cause, method, hostname)))
|
|
60
|
+
|
|
61
|
+
threading.Thread(target=run, name=f"pyfect-dns-{method}", daemon=True).start()
|
|
62
|
+
|
|
63
|
+
return os_wait(register, waits_for="work")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _lookup(host: DomainName, family: IpFamily | None) -> Effect[Sequence[IpAddress], DnsError]:
|
|
67
|
+
def call() -> list[IpAddress]:
|
|
68
|
+
entries = socket.getaddrinfo(host, None, _FAMILIES[family], socket.SOCK_STREAM)
|
|
69
|
+
return [ip_address(str(entry[4][0]).partition("%")[0]) for entry in entries]
|
|
70
|
+
|
|
71
|
+
return _on_thread("lookup", host, call)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _reverse(address: IpAddress) -> Effect[Sequence[str], DnsError]:
|
|
75
|
+
def call() -> list[str]:
|
|
76
|
+
name, aliases, _ = socket.gethostbyaddr(format_ip(address))
|
|
77
|
+
return [name, *aliases]
|
|
78
|
+
|
|
79
|
+
return _on_thread("reverse", format_ip(address), call)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def make_system() -> DnsService:
|
|
83
|
+
"""The system resolver as a ``DnsService``."""
|
|
84
|
+
return make(_lookup, reverse=_reverse)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def layer_system() -> Layer[Dns]:
|
|
88
|
+
"""``Dns`` answered by the operating system's resolver."""
|
|
89
|
+
return layer_succeed(Dns, make_system())
|
pyfect/net/host.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""Host names and endpoints (Effect's ``Host``).
|
|
2
|
+
|
|
3
|
+
A host is an IP address, a scoped IPv6 literal (``fe80::1%eth0``) or a
|
|
4
|
+
``DomainName``: lowercase ASCII, internationalized labels converted to
|
|
5
|
+
their ``xn--`` form, an optional trailing dot. ``HostPort`` pairs a host
|
|
6
|
+
with a port::
|
|
7
|
+
|
|
8
|
+
host.host_port_from_string("DB.internal:5432") # Ok(HostPort("db.internal", 5432))
|
|
9
|
+
host.format_host_port(endpoint) # "db.internal:5432"
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
from collections.abc import Mapping
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from ipaddress import IPv4Address, IPv6Address
|
|
18
|
+
from typing import NewType, TypedDict
|
|
19
|
+
|
|
20
|
+
from pyfect._digits import parse_digits
|
|
21
|
+
from pyfect.net._errors import NetAddressError, address_error
|
|
22
|
+
from pyfect.net.net_address._ip import (
|
|
23
|
+
IpAddress,
|
|
24
|
+
IpAddressInput,
|
|
25
|
+
ScopedIpv6Literal,
|
|
26
|
+
format_ip,
|
|
27
|
+
ip_from_input,
|
|
28
|
+
ip_from_string,
|
|
29
|
+
is_scoped_ipv6_literal,
|
|
30
|
+
scoped_ipv6_literal_from_string,
|
|
31
|
+
)
|
|
32
|
+
from pyfect.result import Ok, Result
|
|
33
|
+
|
|
34
|
+
DomainName = NewType("DomainName", str)
|
|
35
|
+
"""A validated, lowercase ASCII domain name; a trailing dot makes it fully
|
|
36
|
+
qualified, ``"."`` is the root."""
|
|
37
|
+
|
|
38
|
+
type DomainNameInput = DomainName | str
|
|
39
|
+
type Host = IpAddress | ScopedIpv6Literal | DomainName
|
|
40
|
+
type HostInput = Host | IpAddressInput
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class HostPortParts(TypedDict):
|
|
44
|
+
host: HostInput
|
|
45
|
+
port: int
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
_ASCII_NAME_CHARACTER = re.compile(r"[a-z0-9_.-]", re.IGNORECASE)
|
|
49
|
+
_LABEL = re.compile(r"[a-z0-9_](?:[a-z0-9_-]{0,61}[a-z0-9_])?")
|
|
50
|
+
_NUMERIC_LABEL = re.compile(r"[0-9]+|0x[0-9a-f]*")
|
|
51
|
+
_DOMAIN_TEXT = re.compile(r"[a-z0-9_.-]+")
|
|
52
|
+
_NUMERIC_HOST = re.compile(r"[0-9.]+")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _validate(name: str) -> str | None: # noqa: PLR0911
|
|
56
|
+
"""Why ``name`` (lowercase ASCII) is not a domain name, or ``None``."""
|
|
57
|
+
if name == ".":
|
|
58
|
+
return None
|
|
59
|
+
relative = name.removesuffix(".")
|
|
60
|
+
if not relative:
|
|
61
|
+
return "domain name must not be empty"
|
|
62
|
+
if len(relative) > 253:
|
|
63
|
+
return "domain name must be at most 253 characters"
|
|
64
|
+
labels = relative.split(".")
|
|
65
|
+
for label in labels:
|
|
66
|
+
if not label:
|
|
67
|
+
return "domain name labels must not be empty"
|
|
68
|
+
if len(label) > 63:
|
|
69
|
+
return "domain name labels must be at most 63 characters"
|
|
70
|
+
if not _LABEL.fullmatch(label):
|
|
71
|
+
return (
|
|
72
|
+
"domain name labels must contain only letters, digits, hyphens, or "
|
|
73
|
+
"underscores and must not start or end with a hyphen"
|
|
74
|
+
)
|
|
75
|
+
if _NUMERIC_LABEL.fullmatch(labels[-1]):
|
|
76
|
+
return "the last domain name label must not be numeric"
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _to_ascii(text: str) -> str | None:
|
|
81
|
+
"""The name lowercased, or its WHATWG URL host form when it holds
|
|
82
|
+
non-ASCII characters; ``None`` for characters a name cannot hold."""
|
|
83
|
+
if all(_ASCII_NAME_CHARACTER.fullmatch(ch) for ch in text if ch.isascii()):
|
|
84
|
+
if text.isascii():
|
|
85
|
+
return text.lower()
|
|
86
|
+
from pyfect.http import url
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
return url.parse(f"http://{text}").hostname
|
|
90
|
+
except ValueError:
|
|
91
|
+
return None
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def is_domain_name(value: object) -> bool:
|
|
96
|
+
"""Whether ``value`` is already a normalized domain name."""
|
|
97
|
+
return (
|
|
98
|
+
isinstance(value, str) and bool(_DOMAIN_TEXT.fullmatch(value)) and _validate(value) is None
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def is_host(value: object) -> bool:
|
|
103
|
+
return (
|
|
104
|
+
isinstance(value, IPv4Address | IPv6Address)
|
|
105
|
+
or is_scoped_ipv6_literal(value)
|
|
106
|
+
or is_domain_name(value)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def domain_name_from_string(text: str) -> Result[DomainName, NetAddressError]:
|
|
111
|
+
name = _to_ascii(text)
|
|
112
|
+
if name is None:
|
|
113
|
+
return address_error(text, "invalid domain name")
|
|
114
|
+
error = _validate(name)
|
|
115
|
+
return Ok(DomainName(name)) if error is None else address_error(text, error)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def domain_name_from_string_unsafe(text: str) -> DomainName:
|
|
119
|
+
return domain_name_from_string(text).get_or_raise()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def domain_name_from_input(value: DomainNameInput) -> Result[DomainName, NetAddressError]:
|
|
123
|
+
return domain_name_from_string(value)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def domain_name_from_input_unsafe(value: DomainNameInput) -> DomainName:
|
|
127
|
+
return domain_name_from_input(value).get_or_raise()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def host_from_string(text: str) -> Result[Host, NetAddressError]:
|
|
131
|
+
"""A scoped IPv6 literal, an IP address or a domain name, by its text."""
|
|
132
|
+
if "%" in text:
|
|
133
|
+
scoped: Result[Host, NetAddressError] = scoped_ipv6_literal_from_string(text)
|
|
134
|
+
return scoped
|
|
135
|
+
if ":" in text:
|
|
136
|
+
return ip_from_string(text)
|
|
137
|
+
if _NUMERIC_HOST.fullmatch(text):
|
|
138
|
+
address = ip_from_string(text)
|
|
139
|
+
if isinstance(address, Ok):
|
|
140
|
+
return address
|
|
141
|
+
return domain_name_from_string(text)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def host_from_string_unsafe(text: str) -> Host:
|
|
145
|
+
return host_from_string(text).get_or_raise()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def host_from_input(value: HostInput) -> Result[Host, NetAddressError]:
|
|
149
|
+
if isinstance(value, str):
|
|
150
|
+
return host_from_string(value)
|
|
151
|
+
return ip_from_input(value)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def host_from_input_unsafe(value: HostInput) -> Host:
|
|
155
|
+
return host_from_input(value).get_or_raise()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def format_host(host: Host) -> str:
|
|
159
|
+
return host if isinstance(host, str) else format_ip(host)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def is_fully_qualified(name: DomainName) -> bool:
|
|
163
|
+
return name.endswith(".")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def to_fully_qualified(name: DomainName) -> DomainName:
|
|
167
|
+
return name if is_fully_qualified(name) else DomainName(f"{name}.")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def to_relative(name: DomainName) -> DomainName:
|
|
171
|
+
return DomainName(name[:-1]) if len(name) > 1 and is_fully_qualified(name) else name
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dataclass(frozen=True, slots=True)
|
|
175
|
+
class HostPort:
|
|
176
|
+
"""A host and port; ``str`` gives ``host:port`` (``[ipv6]:port``)."""
|
|
177
|
+
|
|
178
|
+
host: Host
|
|
179
|
+
port: int
|
|
180
|
+
|
|
181
|
+
def __post_init__(self) -> None:
|
|
182
|
+
if not is_host(self.host):
|
|
183
|
+
raise NetAddressError("expected a host", self.host)
|
|
184
|
+
if not _is_port(self.port):
|
|
185
|
+
raise NetAddressError("port must be an integer from 0 through 65535", self.port)
|
|
186
|
+
|
|
187
|
+
def __str__(self) -> str:
|
|
188
|
+
return format_host_port(self)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
type HostPortInput = HostPort | str | HostPortParts
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _is_port(port: object) -> bool:
|
|
195
|
+
return isinstance(port, int) and not isinstance(port, bool) and 0 <= port <= 0xFFFF
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def host_port(host: Host, port: int) -> Result[HostPort, NetAddressError]:
|
|
199
|
+
if not is_host(host):
|
|
200
|
+
return address_error(host, "expected a host")
|
|
201
|
+
if not _is_port(port):
|
|
202
|
+
return address_error(port, "port must be an integer from 0 through 65535")
|
|
203
|
+
return Ok(HostPort(host, port))
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def host_port_unsafe(host: Host, port: int) -> HostPort:
|
|
207
|
+
return host_port(host, port).get_or_raise()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def host_port_from_string(text: str) -> Result[HostPort, NetAddressError]: # noqa: PLR0911
|
|
211
|
+
if text.startswith("["):
|
|
212
|
+
end = text.find("]")
|
|
213
|
+
if end == -1 or text[end + 1 : end + 2] != ":":
|
|
214
|
+
return address_error(text, "expected [IPv6]:port")
|
|
215
|
+
host_text, port_text = text[1:end], text[end + 2 :]
|
|
216
|
+
if ":" not in host_text:
|
|
217
|
+
return address_error(text, "only IPv6 addresses use brackets")
|
|
218
|
+
else:
|
|
219
|
+
separator = text.rfind(":")
|
|
220
|
+
if separator == -1:
|
|
221
|
+
return address_error(text, "expected host:port")
|
|
222
|
+
if text.find(":") != separator:
|
|
223
|
+
return address_error(text, "IPv6 addresses must be bracketed")
|
|
224
|
+
host_text, port_text = text[:separator], text[separator + 1 :]
|
|
225
|
+
port = parse_digits(port_text, unpadded=True)
|
|
226
|
+
if port is None:
|
|
227
|
+
return address_error(text, "port must be an unpadded decimal integer")
|
|
228
|
+
if not _is_port(port):
|
|
229
|
+
return address_error(text, "port must be an integer from 0 through 65535")
|
|
230
|
+
return host_from_string(host_text).map(lambda host: HostPort(host, port))
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def host_port_from_string_unsafe(text: str) -> HostPort:
|
|
234
|
+
return host_port_from_string(text).get_or_raise()
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def host_port_from_input(value: HostPortInput) -> Result[HostPort, NetAddressError]:
|
|
238
|
+
"""An endpoint, its text, or ``{"host": ..., "port": ...}``."""
|
|
239
|
+
if isinstance(value, HostPort):
|
|
240
|
+
return Ok(value)
|
|
241
|
+
if isinstance(value, str):
|
|
242
|
+
return host_port_from_string(value)
|
|
243
|
+
port = value["port"]
|
|
244
|
+
return host_from_input(value["host"]).flat_map(lambda host: host_port(host, port))
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def host_port_from_input_unsafe(value: HostPortInput) -> HostPort:
|
|
248
|
+
return host_port_from_input(value).get_or_raise()
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def format_host_port(endpoint: HostPort) -> str:
|
|
252
|
+
host = format_host(endpoint.host)
|
|
253
|
+
return f"[{host}]:{endpoint.port}" if ":" in host else f"{host}:{endpoint.port}"
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
type HostsTable = Mapping[DomainName, tuple[IpAddress, ...]]
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def parse_hosts_file(text: str) -> HostsTable:
|
|
260
|
+
"""A hosts file's addresses by relative name, in file order; lines and
|
|
261
|
+
names that do not parse are skipped."""
|
|
262
|
+
hosts: dict[DomainName, list[IpAddress]] = {}
|
|
263
|
+
for line in re.split(r"\r?\n", text):
|
|
264
|
+
fields = line.partition("#")[0].split()
|
|
265
|
+
if not fields:
|
|
266
|
+
continue
|
|
267
|
+
address = ip_from_string(fields[0])
|
|
268
|
+
if not isinstance(address, Ok):
|
|
269
|
+
continue
|
|
270
|
+
for name in fields[1:]:
|
|
271
|
+
domain = domain_name_from_string(name)
|
|
272
|
+
if not isinstance(domain, Ok):
|
|
273
|
+
continue
|
|
274
|
+
addresses = hosts.setdefault(to_relative(domain.value), [])
|
|
275
|
+
if address.value not in addresses:
|
|
276
|
+
addresses.append(address.value)
|
|
277
|
+
return {name: tuple(addresses) for name, addresses in hosts.items()}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
__all__ = [
|
|
281
|
+
"DomainName",
|
|
282
|
+
"DomainNameInput",
|
|
283
|
+
"Host",
|
|
284
|
+
"HostInput",
|
|
285
|
+
"HostPort",
|
|
286
|
+
"HostPortInput",
|
|
287
|
+
"HostPortParts",
|
|
288
|
+
"HostsTable",
|
|
289
|
+
"domain_name_from_input",
|
|
290
|
+
"domain_name_from_input_unsafe",
|
|
291
|
+
"domain_name_from_string",
|
|
292
|
+
"domain_name_from_string_unsafe",
|
|
293
|
+
"format_host",
|
|
294
|
+
"format_host_port",
|
|
295
|
+
"host_from_input",
|
|
296
|
+
"host_from_input_unsafe",
|
|
297
|
+
"host_from_string",
|
|
298
|
+
"host_from_string_unsafe",
|
|
299
|
+
"host_port",
|
|
300
|
+
"host_port_from_input",
|
|
301
|
+
"host_port_from_input_unsafe",
|
|
302
|
+
"host_port_from_string",
|
|
303
|
+
"host_port_from_string_unsafe",
|
|
304
|
+
"host_port_unsafe",
|
|
305
|
+
"is_domain_name",
|
|
306
|
+
"is_fully_qualified",
|
|
307
|
+
"is_host",
|
|
308
|
+
"parse_hosts_file",
|
|
309
|
+
"to_fully_qualified",
|
|
310
|
+
"to_relative",
|
|
311
|
+
]
|