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/process/_child.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""A spawned child process: its exit, its signals, and how it is killed
|
|
2
|
+
(the process-group handling of Effect's ``NodeChildProcessSpawner``).
|
|
3
|
+
|
|
4
|
+
A detached child (the default on POSIX) leads a process group of its own,
|
|
5
|
+
and killing it signals the whole group. Without ``force_kill_after``, a kill
|
|
6
|
+
waits for the leader to exit with no time limit, then gives the rest of the
|
|
7
|
+
group up to a second; with it, ``SIGKILL`` follows after that long and the
|
|
8
|
+
group gets a final second. Waits poll the group on the scheduler's
|
|
9
|
+
real-time timers, never on the ``Clock``. A group whose leader has exited
|
|
10
|
+
is found by its numeric id, so if that id were reused before cleanup an
|
|
11
|
+
unrelated group could be signalled.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import contextlib
|
|
17
|
+
import errno
|
|
18
|
+
import os
|
|
19
|
+
import signal
|
|
20
|
+
import subprocess
|
|
21
|
+
import threading
|
|
22
|
+
import time
|
|
23
|
+
from collections.abc import Callable
|
|
24
|
+
from typing import Never
|
|
25
|
+
|
|
26
|
+
from pyfect._core import Effect
|
|
27
|
+
from pyfect.duration import Seconds
|
|
28
|
+
from pyfect.effect import callback, fail, gen, succeed, suspend, sync, try_
|
|
29
|
+
from pyfect.platform_error import PlatformError, from_exception, system_error
|
|
30
|
+
from pyfect.process._command import StandardCommand
|
|
31
|
+
from pyfect.process._os import Latch
|
|
32
|
+
from pyfect.scheduler import Cancel, CurrentScheduler, Scheduler
|
|
33
|
+
|
|
34
|
+
GROUP_GRACE = Seconds(1.0)
|
|
35
|
+
"""How long the rest of a process group gets to exit after its leader."""
|
|
36
|
+
|
|
37
|
+
_POLL = Seconds(0.01)
|
|
38
|
+
|
|
39
|
+
_SIGKILL: signal.Signals = getattr(signal, "SIGKILL", signal.SIGTERM)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Child:
|
|
43
|
+
"""A started process, watched by a thread that waits for its exit."""
|
|
44
|
+
|
|
45
|
+
__slots__ = ("_exit", "command", "command_line", "pid", "popen", "referenced")
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self, command: StandardCommand, popen: subprocess.Popen[bytes], command_line: str
|
|
49
|
+
) -> None:
|
|
50
|
+
self.command = command
|
|
51
|
+
self.popen = popen
|
|
52
|
+
self.pid = popen.pid
|
|
53
|
+
self.command_line = command_line
|
|
54
|
+
self.referenced = True
|
|
55
|
+
self._exit: Latch[int] = Latch(waits_for="event")
|
|
56
|
+
threading.Thread(target=self._watch, name="pyfect-process-exit", daemon=True).start()
|
|
57
|
+
|
|
58
|
+
# --- state ------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
def returncode(self) -> int | None:
|
|
61
|
+
"""``subprocess``'s code once the leader has exited (``-N`` for
|
|
62
|
+
signal ``N``)."""
|
|
63
|
+
code = self._exit.get()
|
|
64
|
+
return None if code is None else code[0]
|
|
65
|
+
|
|
66
|
+
def exited(self) -> bool:
|
|
67
|
+
return self._exit.get() is not None
|
|
68
|
+
|
|
69
|
+
def after_exit(self, callback: Callable[[], None]) -> None:
|
|
70
|
+
"""Call ``callback`` once the leader has exited (now if it has)."""
|
|
71
|
+
self._exit.on_set(callback)
|
|
72
|
+
|
|
73
|
+
def wait_exit(self) -> Effect[int]:
|
|
74
|
+
return self._exit.wait()
|
|
75
|
+
|
|
76
|
+
def owns_group(self) -> bool:
|
|
77
|
+
"""A non-detached leader shares its parent's group, so once it has
|
|
78
|
+
exited there is no group of its own left."""
|
|
79
|
+
return os.name != "nt" and (self.command.options.detached is not False or not self.exited())
|
|
80
|
+
|
|
81
|
+
def alive(self) -> bool:
|
|
82
|
+
"""The leader has not exited, or a member of its group still
|
|
83
|
+
exists."""
|
|
84
|
+
if not self.exited():
|
|
85
|
+
return True
|
|
86
|
+
if not self.owns_group():
|
|
87
|
+
return False
|
|
88
|
+
try:
|
|
89
|
+
os.killpg(self.pid, 0)
|
|
90
|
+
except PermissionError:
|
|
91
|
+
# macOS answers EPERM while the group's remaining members are
|
|
92
|
+
# zombies.
|
|
93
|
+
return True
|
|
94
|
+
except OSError:
|
|
95
|
+
return False
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
def _watch(self) -> None:
|
|
99
|
+
code = self.popen.wait()
|
|
100
|
+
if code > 0 and self.command.options.detached is not False and os.name != "nt":
|
|
101
|
+
# Best effort, as Node's exit listener: a failed leader takes its
|
|
102
|
+
# group with it.
|
|
103
|
+
with contextlib.suppress(OSError):
|
|
104
|
+
os.killpg(self.pid, self.command.options.kill_signal or signal.SIGTERM)
|
|
105
|
+
self._exit.set(code)
|
|
106
|
+
|
|
107
|
+
# --- signals ----------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def _signal_process(self, sig: signal.Signals) -> None:
|
|
110
|
+
if self.exited():
|
|
111
|
+
raise ProcessLookupError(errno.ESRCH, "Failed to kill child process")
|
|
112
|
+
os.kill(self.pid, sig)
|
|
113
|
+
|
|
114
|
+
def _signal_group(self, sig: signal.Signals) -> None:
|
|
115
|
+
if self.owns_group():
|
|
116
|
+
try:
|
|
117
|
+
os.killpg(self.pid, sig)
|
|
118
|
+
return
|
|
119
|
+
except OSError as error:
|
|
120
|
+
# Only zombies remain in an exited leader's group.
|
|
121
|
+
if self.exited() and error.errno == errno.EPERM:
|
|
122
|
+
return
|
|
123
|
+
self._signal_process(sig)
|
|
124
|
+
|
|
125
|
+
def signal(self, sig: signal.Signals) -> Effect[None, PlatformError]:
|
|
126
|
+
return try_(lambda: self._signal_group(sig), self._error("kill"))
|
|
127
|
+
|
|
128
|
+
def _error(self, method: str) -> Callable[[Exception], PlatformError]:
|
|
129
|
+
return lambda error: from_exception(
|
|
130
|
+
error, module="ChildProcess", method=method, path_or_descriptor=self.command_line
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def _await_group(self, timeout: Seconds) -> Effect[None]:
|
|
134
|
+
"""Wait, at most ``timeout``, for the leader and its group to be
|
|
135
|
+
gone, polling on the scheduler's real-time timers."""
|
|
136
|
+
|
|
137
|
+
def start(scheduler: Scheduler) -> Effect[None]:
|
|
138
|
+
def register(resume: Callable[[Effect[None]], None]) -> Effect[None]:
|
|
139
|
+
deadline = time.monotonic() + timeout
|
|
140
|
+
pending: list[Cancel] = []
|
|
141
|
+
|
|
142
|
+
def poll() -> None:
|
|
143
|
+
if time.monotonic() >= deadline or not self.alive():
|
|
144
|
+
resume(succeed(None))
|
|
145
|
+
else:
|
|
146
|
+
pending[:] = [scheduler.call_later(_POLL, poll)]
|
|
147
|
+
|
|
148
|
+
def stop() -> None:
|
|
149
|
+
for cancel in pending:
|
|
150
|
+
cancel()
|
|
151
|
+
|
|
152
|
+
poll()
|
|
153
|
+
return sync(stop)
|
|
154
|
+
|
|
155
|
+
return callback(register)
|
|
156
|
+
|
|
157
|
+
return CurrentScheduler().flat_map(start)
|
|
158
|
+
|
|
159
|
+
def terminate(
|
|
160
|
+
self, kill_signal: signal.Signals | None, force_kill_after: Seconds | None
|
|
161
|
+
) -> Effect[None, PlatformError]:
|
|
162
|
+
def body():
|
|
163
|
+
yield from self.signal(kill_signal or signal.SIGTERM)
|
|
164
|
+
if force_kill_after is None:
|
|
165
|
+
yield from self.wait_exit()
|
|
166
|
+
yield from self._await_group(GROUP_GRACE)
|
|
167
|
+
else:
|
|
168
|
+
yield from self._await_group(force_kill_after)
|
|
169
|
+
if self.alive():
|
|
170
|
+
yield from self.signal(_SIGKILL)
|
|
171
|
+
yield from self._await_group(GROUP_GRACE)
|
|
172
|
+
yield from self.wait_exit()
|
|
173
|
+
|
|
174
|
+
return gen(body)
|
|
175
|
+
|
|
176
|
+
def release(self) -> Effect[None]:
|
|
177
|
+
"""What the scope does when it closes (Effect's release of the
|
|
178
|
+
spawn): kill a referenced process, and the group of an exited one
|
|
179
|
+
unless a successful non-detached leader left none."""
|
|
180
|
+
options = self.command.options
|
|
181
|
+
|
|
182
|
+
def kill() -> Effect[None]:
|
|
183
|
+
return self.terminate(options.kill_signal, options.force_kill_after).ignore()
|
|
184
|
+
|
|
185
|
+
def decide() -> Effect[None]:
|
|
186
|
+
code = self.returncode()
|
|
187
|
+
if code is None:
|
|
188
|
+
return kill() if self.referenced else succeed(None)
|
|
189
|
+
if code > 0:
|
|
190
|
+
return kill()
|
|
191
|
+
if self.referenced and os.name != "nt" and options.detached is not False:
|
|
192
|
+
return kill()
|
|
193
|
+
return succeed(None)
|
|
194
|
+
|
|
195
|
+
return suspend(decide)
|
|
196
|
+
|
|
197
|
+
def exit_code_error(self, code: int) -> Effect[Never, PlatformError]:
|
|
198
|
+
"""A signal ended the leader: ``exit_code`` fails."""
|
|
199
|
+
name = signal.Signals(-code).name
|
|
200
|
+
return fail(
|
|
201
|
+
system_error(
|
|
202
|
+
"Unknown",
|
|
203
|
+
module="ChildProcess",
|
|
204
|
+
method="exit_code",
|
|
205
|
+
description=f"Process interrupted due to receipt of signal: '{name}'",
|
|
206
|
+
path_or_descriptor=self.command_line,
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def command_line(commands: tuple[StandardCommand, ...]) -> str:
|
|
212
|
+
"""How errors name a pipeline: ``cmd args | cmd args``."""
|
|
213
|
+
return " | ".join(" ".join((c.command, *c.args)) for c in commands)
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""Commands: descriptions of child processes before they start (Effect's
|
|
2
|
+
``ChildProcess``).
|
|
3
|
+
|
|
4
|
+
A command is an ``Effect``: running it asks the ``ChildProcessSpawner``
|
|
5
|
+
service to start it, in the current ``Scope``, and gives back a
|
|
6
|
+
``ChildProcessHandle``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import signal
|
|
14
|
+
from collections.abc import Mapping, Sequence
|
|
15
|
+
from dataclasses import dataclass, field, replace
|
|
16
|
+
from types import MappingProxyType
|
|
17
|
+
from typing import TYPE_CHECKING, Any, Literal, Never, final
|
|
18
|
+
|
|
19
|
+
from pyfect._core import Effect
|
|
20
|
+
from pyfect.duration import Duration, Seconds, to_seconds
|
|
21
|
+
from pyfect.platform_error import PlatformError
|
|
22
|
+
from pyfect.sink import Sink
|
|
23
|
+
from pyfect.sink._sink import require_sink
|
|
24
|
+
from pyfect.stream import Stream
|
|
25
|
+
from pyfect.stream._base import require_stream
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from pyfect._effect_base import Step
|
|
29
|
+
from pyfect._fiber import Fiber
|
|
30
|
+
|
|
31
|
+
# Named in the base class's quoted type arguments.
|
|
32
|
+
from pyfect.process._handle import ChildProcessHandle # noqa: F401
|
|
33
|
+
from pyfect.process._spawner import ChildProcessSpawner # noqa: F401
|
|
34
|
+
from pyfect.scope import Scope # noqa: F401
|
|
35
|
+
|
|
36
|
+
type StdioKind = Literal["pipe", "inherit", "ignore"]
|
|
37
|
+
"""How the operating system sets up a standard stream: a pipe to the
|
|
38
|
+
parent, the parent's own stream, or nothing (``/dev/null``)."""
|
|
39
|
+
|
|
40
|
+
type CommandInput = StdioKind | Stream[bytes, PlatformError]
|
|
41
|
+
"""A child's standard input: a ``StdioKind``, or a stream written into
|
|
42
|
+
it."""
|
|
43
|
+
|
|
44
|
+
type CommandOutput = StdioKind | Sink[bytes, bytes, Never, PlatformError]
|
|
45
|
+
"""A child's standard output or error: a ``StdioKind``, or a sink its
|
|
46
|
+
bytes are transduced through as the handle's stream is read."""
|
|
47
|
+
|
|
48
|
+
type PipeFrom = Literal["stdout", "stderr", "all"] | int
|
|
49
|
+
"""Which output of a command a pipe reads: an ``int`` is an additional
|
|
50
|
+
output descriptor (3 or more)."""
|
|
51
|
+
|
|
52
|
+
type PipeTo = Literal["stdin"] | int
|
|
53
|
+
"""Which input of a command a pipe writes: an ``int`` is an additional
|
|
54
|
+
input descriptor (3 or more)."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True, slots=True)
|
|
58
|
+
class StdinConfig:
|
|
59
|
+
"""A child's standard input; with ``end_on_done`` (the default) the pipe
|
|
60
|
+
closes once a given stream ends."""
|
|
61
|
+
|
|
62
|
+
stream: CommandInput = "pipe"
|
|
63
|
+
end_on_done: bool = True
|
|
64
|
+
|
|
65
|
+
def __post_init__(self) -> None:
|
|
66
|
+
if not isinstance(self.stream, str):
|
|
67
|
+
require_stream(self.stream, "stream")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True, slots=True)
|
|
71
|
+
class InputFd:
|
|
72
|
+
"""An additional descriptor the parent writes and the child reads,
|
|
73
|
+
fed by ``stream`` if given."""
|
|
74
|
+
|
|
75
|
+
stream: Stream[bytes, PlatformError] | None = None
|
|
76
|
+
|
|
77
|
+
def __post_init__(self) -> None:
|
|
78
|
+
if self.stream is not None:
|
|
79
|
+
require_stream(self.stream, "stream")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True, slots=True)
|
|
83
|
+
class OutputFd:
|
|
84
|
+
"""An additional descriptor the child writes and the parent reads,
|
|
85
|
+
transduced through ``sink`` if given."""
|
|
86
|
+
|
|
87
|
+
sink: Sink[bytes, bytes, Never, PlatformError] | None = None
|
|
88
|
+
|
|
89
|
+
def __post_init__(self) -> None:
|
|
90
|
+
if self.sink is not None:
|
|
91
|
+
require_sink(self.sink, "sink")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
type AdditionalFd = InputFd | OutputFd
|
|
95
|
+
|
|
96
|
+
_FIRST_EXTRA_FD = 3
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _no_fds() -> Mapping[int, AdditionalFd]:
|
|
100
|
+
return MappingProxyType({})
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
104
|
+
class CommandOptions:
|
|
105
|
+
"""How a command runs; see ``make`` for each option."""
|
|
106
|
+
|
|
107
|
+
cwd: str | None = None
|
|
108
|
+
env: Mapping[str, str | None] | None = None
|
|
109
|
+
extend_env: bool = False
|
|
110
|
+
shell: bool | str = False
|
|
111
|
+
detached: bool | None = None
|
|
112
|
+
windows_hide: bool | None = None
|
|
113
|
+
stdin: StdinConfig = StdinConfig()
|
|
114
|
+
stdout: CommandOutput = "pipe"
|
|
115
|
+
stderr: CommandOutput = "pipe"
|
|
116
|
+
additional_fds: Mapping[int, AdditionalFd] = field(default_factory=_no_fds)
|
|
117
|
+
kill_signal: signal.Signals | None = None
|
|
118
|
+
force_kill_after: Seconds | None = None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
122
|
+
class PipeOptions:
|
|
123
|
+
"""Which output of the left command feeds which input of the right."""
|
|
124
|
+
|
|
125
|
+
from_: PipeFrom = "stdout"
|
|
126
|
+
to: PipeTo = "stdin"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class _CommandBase(Effect["ChildProcessHandle", PlatformError, "ChildProcessSpawner | Scope"]):
|
|
130
|
+
__slots__ = ()
|
|
131
|
+
|
|
132
|
+
def _step(self, fiber: Fiber[Any, Any]) -> Step:
|
|
133
|
+
from pyfect.process._spawner import ChildProcessSpawner
|
|
134
|
+
|
|
135
|
+
command: Command = self # pyright: ignore[reportAssignmentType]
|
|
136
|
+
return ChildProcessSpawner().flat_map(lambda spawner: spawner.spawn(command))
|
|
137
|
+
|
|
138
|
+
def pipe_to(
|
|
139
|
+
self, that: Command, *, from_: PipeFrom = "stdout", to: PipeTo = "stdin"
|
|
140
|
+
) -> PipedCommand:
|
|
141
|
+
"""Feed ``from_`` of this command (standard output by default) into
|
|
142
|
+
``to`` of ``that`` (standard input by default)."""
|
|
143
|
+
if not isinstance(that, (StandardCommand, PipedCommand)): # pyright: ignore[reportUnnecessaryIsInstance]
|
|
144
|
+
raise TypeError(f"that must be a command, got {type(that).__name__}")
|
|
145
|
+
if isinstance(from_, int) and from_ < _FIRST_EXTRA_FD:
|
|
146
|
+
from_ = "stdout"
|
|
147
|
+
if isinstance(to, int) and to < _FIRST_EXTRA_FD:
|
|
148
|
+
to = "stdin"
|
|
149
|
+
left: Command = self # pyright: ignore[reportAssignmentType]
|
|
150
|
+
return PipedCommand(left, that, PipeOptions(from_=from_, to=to))
|
|
151
|
+
|
|
152
|
+
def prefix(self, command: str, args: Sequence[str] = ()) -> Command:
|
|
153
|
+
"""Run this command through ``command args...`` (``time``, ``nice``,
|
|
154
|
+
...); in a pipeline, the leftmost command only."""
|
|
155
|
+
this: Command = self # pyright: ignore[reportAssignmentType]
|
|
156
|
+
return _prefix(this, command, tuple(args))
|
|
157
|
+
|
|
158
|
+
def set_cwd(self, cwd: str | os.PathLike[str]) -> Command:
|
|
159
|
+
"""Run in ``cwd``; in a pipeline, every command."""
|
|
160
|
+
this: Command = self # pyright: ignore[reportAssignmentType]
|
|
161
|
+
return _set_cwd(this, os.fspath(cwd))
|
|
162
|
+
|
|
163
|
+
def set_env(self, env: Mapping[str, str]) -> Command:
|
|
164
|
+
"""Add ``env`` to the command's environment, replacing variables of
|
|
165
|
+
the same name; in a pipeline, every command's."""
|
|
166
|
+
this: Command = self # pyright: ignore[reportAssignmentType]
|
|
167
|
+
return _set_env(this, dict(env))
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@final
|
|
171
|
+
class StandardCommand(_CommandBase):
|
|
172
|
+
"""One executable with its arguments and options."""
|
|
173
|
+
|
|
174
|
+
__slots__ = ("args", "command", "options")
|
|
175
|
+
|
|
176
|
+
def __init__(self, command: str, args: tuple[str, ...], options: CommandOptions) -> None:
|
|
177
|
+
self.command = command
|
|
178
|
+
self.args = args
|
|
179
|
+
self.options = options
|
|
180
|
+
|
|
181
|
+
def __repr__(self) -> str:
|
|
182
|
+
return f"StandardCommand({self.command!r}, {list(self.args)!r})"
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@final
|
|
186
|
+
class PipedCommand(_CommandBase):
|
|
187
|
+
"""``left``'s output feeding ``right``'s input."""
|
|
188
|
+
|
|
189
|
+
__slots__ = ("left", "options", "right")
|
|
190
|
+
|
|
191
|
+
def __init__(self, left: Command, right: Command, options: PipeOptions) -> None:
|
|
192
|
+
self.left = left
|
|
193
|
+
self.right = right
|
|
194
|
+
self.options = options
|
|
195
|
+
|
|
196
|
+
def __repr__(self) -> str:
|
|
197
|
+
return f"PipedCommand({self.left!r}, {self.right!r})"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
type Command = StandardCommand | PipedCommand
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def make(
|
|
204
|
+
command: str,
|
|
205
|
+
args: Sequence[str] = (),
|
|
206
|
+
*,
|
|
207
|
+
cwd: str | os.PathLike[str] | None = None,
|
|
208
|
+
env: Mapping[str, str | None] | None = None,
|
|
209
|
+
extend_env: bool = False,
|
|
210
|
+
shell: bool | str = False,
|
|
211
|
+
detached: bool | None = None,
|
|
212
|
+
windows_hide: bool | None = None,
|
|
213
|
+
stdin: CommandInput | StdinConfig = "pipe",
|
|
214
|
+
stdout: CommandOutput = "pipe",
|
|
215
|
+
stderr: CommandOutput = "pipe",
|
|
216
|
+
additional_fds: Mapping[int, AdditionalFd] | None = None,
|
|
217
|
+
kill_signal: signal.Signals | None = None,
|
|
218
|
+
force_kill_after: Duration | None = None,
|
|
219
|
+
) -> StandardCommand:
|
|
220
|
+
"""A command running ``command`` with ``args`` (Effect's array form of
|
|
221
|
+
``ChildProcess.make``; ``shlex.split`` turns a command line into one).
|
|
222
|
+
|
|
223
|
+
- ``cwd``: the working directory, checked to exist when spawned.
|
|
224
|
+
- ``env``: the environment; it replaces the parent's unless
|
|
225
|
+
``extend_env``, which adds it over the parent's. A ``None`` value
|
|
226
|
+
removes the variable.
|
|
227
|
+
- ``shell``: run ``command`` as a line of shell with ``/bin/sh``
|
|
228
|
+
(``cmd.exe`` on Windows) or the shell given; each of ``args`` is
|
|
229
|
+
quoted and appended as one literal word.
|
|
230
|
+
- ``detached``: start a process group of its own, so killing the
|
|
231
|
+
command kills its descendants (the default except on Windows).
|
|
232
|
+
- ``windows_hide``: hide the console window on Windows (the default
|
|
233
|
+
unless detached).
|
|
234
|
+
- ``stdin``, ``stdout``, ``stderr``: see ``CommandInput``,
|
|
235
|
+
``CommandOutput`` and ``StdinConfig``; each is a pipe by default.
|
|
236
|
+
- ``additional_fds``: descriptors from 3 up, by number; lower ones are skipped.
|
|
237
|
+
- ``kill_signal``, ``force_kill_after``: how the scope ending the
|
|
238
|
+
command kills it: the signal (``SIGTERM`` by default), then
|
|
239
|
+
``SIGKILL`` after this long if the process group is still alive.
|
|
240
|
+
"""
|
|
241
|
+
stdin_config = stdin if isinstance(stdin, StdinConfig) else StdinConfig(_input(stdin, "stdin"))
|
|
242
|
+
fds = MappingProxyType(
|
|
243
|
+
{fd: config for fd, config in (additional_fds or {}).items() if fd >= _FIRST_EXTRA_FD}
|
|
244
|
+
)
|
|
245
|
+
for fd, config in fds.items():
|
|
246
|
+
if not isinstance(config, (InputFd, OutputFd)): # pyright: ignore[reportUnnecessaryIsInstance]
|
|
247
|
+
raise TypeError(f"additional_fds[{fd}] must be an InputFd or OutputFd")
|
|
248
|
+
options = CommandOptions(
|
|
249
|
+
cwd=None if cwd is None else os.fspath(cwd),
|
|
250
|
+
env=None if env is None else dict(env),
|
|
251
|
+
extend_env=extend_env,
|
|
252
|
+
shell=shell,
|
|
253
|
+
detached=detached,
|
|
254
|
+
windows_hide=windows_hide,
|
|
255
|
+
stdin=stdin_config,
|
|
256
|
+
stdout=_output(stdout, "stdout"),
|
|
257
|
+
stderr=_output(stderr, "stderr"),
|
|
258
|
+
additional_fds=fds,
|
|
259
|
+
kill_signal=kill_signal,
|
|
260
|
+
force_kill_after=None if force_kill_after is None else to_seconds(force_kill_after),
|
|
261
|
+
)
|
|
262
|
+
return StandardCommand(command, tuple(args), options)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def with_options(command: StandardCommand, **changes: Any) -> StandardCommand:
|
|
266
|
+
"""``command`` with some of its ``CommandOptions`` replaced."""
|
|
267
|
+
return StandardCommand(command.command, command.args, replace(command.options, **changes))
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _input(value: CommandInput, name: str) -> CommandInput:
|
|
271
|
+
if not isinstance(value, str):
|
|
272
|
+
require_stream(value, name)
|
|
273
|
+
return value
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _output(value: CommandOutput, name: str) -> CommandOutput:
|
|
277
|
+
if not isinstance(value, str):
|
|
278
|
+
require_sink(value, name)
|
|
279
|
+
return value
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _prefix(command: Command, executable: str, args: tuple[str, ...]) -> Command:
|
|
283
|
+
match command:
|
|
284
|
+
case StandardCommand():
|
|
285
|
+
return StandardCommand(
|
|
286
|
+
executable, (*args, command.command, *command.args), command.options
|
|
287
|
+
)
|
|
288
|
+
case PipedCommand():
|
|
289
|
+
return PipedCommand(
|
|
290
|
+
_prefix(command.left, executable, args), command.right, command.options
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _set_cwd(command: Command, cwd: str) -> Command:
|
|
295
|
+
match command:
|
|
296
|
+
case StandardCommand():
|
|
297
|
+
return with_options(command, cwd=cwd)
|
|
298
|
+
case PipedCommand():
|
|
299
|
+
return PipedCommand(
|
|
300
|
+
_set_cwd(command.left, cwd), _set_cwd(command.right, cwd), command.options
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _set_env(command: Command, env: dict[str, str]) -> Command:
|
|
305
|
+
match command:
|
|
306
|
+
case StandardCommand():
|
|
307
|
+
merged = env if command.options.env is None else {**command.options.env, **env}
|
|
308
|
+
return with_options(command, env=merged)
|
|
309
|
+
case PipedCommand():
|
|
310
|
+
return PipedCommand(
|
|
311
|
+
_set_env(command.left, env), _set_env(command.right, env), command.options
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
@dataclass(frozen=True, slots=True)
|
|
316
|
+
class FlattenedPipeline:
|
|
317
|
+
"""A pipeline's commands from left to right, and the options of each
|
|
318
|
+
pipe between two of them."""
|
|
319
|
+
|
|
320
|
+
commands: tuple[StandardCommand, *tuple[StandardCommand, ...]]
|
|
321
|
+
pipe_options: tuple[PipeOptions, ...]
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def flatten_command(command: Command) -> FlattenedPipeline:
|
|
325
|
+
"""The commands of a pipeline in order, with the options of each pipe."""
|
|
326
|
+
commands: list[StandardCommand] = []
|
|
327
|
+
pipes: list[PipeOptions] = []
|
|
328
|
+
|
|
329
|
+
def walk(cmd: Command) -> None:
|
|
330
|
+
match cmd:
|
|
331
|
+
case StandardCommand():
|
|
332
|
+
commands.append(cmd)
|
|
333
|
+
case PipedCommand():
|
|
334
|
+
walk(cmd.left)
|
|
335
|
+
pipes.append(cmd.options)
|
|
336
|
+
walk(cmd.right)
|
|
337
|
+
|
|
338
|
+
walk(command)
|
|
339
|
+
first, *rest = commands
|
|
340
|
+
return FlattenedPipeline((first, *rest), tuple(pipes))
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
_FD_NAME = re.compile(r"fd([0-9]+)")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def parse_fd_name(name: str) -> int | None:
|
|
347
|
+
"""The descriptor ``"fd3"`` names (3), or ``None`` unless the name is
|
|
348
|
+
``fd`` and a number from 3 up."""
|
|
349
|
+
match = _FD_NAME.fullmatch(name)
|
|
350
|
+
if match is None:
|
|
351
|
+
return None
|
|
352
|
+
fd = int(match[1])
|
|
353
|
+
return fd if fd >= 3 else None
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def fd_name(fd: int) -> str:
|
|
357
|
+
"""``"fd3"`` for 3."""
|
|
358
|
+
return f"fd{fd}"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""A running child process (Effect's ``ChildProcessHandle``)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import signal
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Never, NewType, Protocol
|
|
9
|
+
|
|
10
|
+
from pyfect._core import Effect
|
|
11
|
+
from pyfect._effect_args import require_effect
|
|
12
|
+
from pyfect.duration import Duration
|
|
13
|
+
from pyfect.platform_error import PlatformError
|
|
14
|
+
from pyfect.sink import Sink
|
|
15
|
+
from pyfect.sink._sink import require_sink
|
|
16
|
+
from pyfect.stream import Stream
|
|
17
|
+
from pyfect.stream._base import require_stream
|
|
18
|
+
|
|
19
|
+
ExitCode = NewType("ExitCode", int)
|
|
20
|
+
"""The status a child process exited with."""
|
|
21
|
+
|
|
22
|
+
ProcessId = NewType("ProcessId", int)
|
|
23
|
+
"""A child process's operating-system identifier."""
|
|
24
|
+
|
|
25
|
+
type Reref = Effect[None, PlatformError]
|
|
26
|
+
"""Given back by ``ChildProcessHandle.unref``: running it makes the scope
|
|
27
|
+
own the process again."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Kill(Protocol):
|
|
31
|
+
"""Kills a child process with ``kill_signal`` (``SIGTERM`` by default)
|
|
32
|
+
and waits for it. With ``force_kill_after``, sends ``SIGKILL`` if the
|
|
33
|
+
process (group) is still alive after that long; without it, waits with
|
|
34
|
+
no time limit."""
|
|
35
|
+
|
|
36
|
+
def __call__(
|
|
37
|
+
self, *, kill_signal: signal.Signals | None = None, force_kill_after: Duration | None = None
|
|
38
|
+
) -> Effect[None, PlatformError]: ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True, slots=True, kw_only=True, eq=False)
|
|
42
|
+
class ChildProcessHandle:
|
|
43
|
+
"""A running child process; the constructor is Effect's ``makeHandle``.
|
|
44
|
+
|
|
45
|
+
- ``exit_code``: waits for the exit; fails if a signal ended the
|
|
46
|
+
process, or with the error of a stream feeding one of its inputs.
|
|
47
|
+
- ``stdin``: a sink writing standard input.
|
|
48
|
+
- ``stdout``, ``stderr``: byte streams of its output; ``all`` both,
|
|
49
|
+
interleaved. Reading one alongside ``all`` splits the output between
|
|
50
|
+
them.
|
|
51
|
+
- ``get_input_fd`` / ``get_output_fd``: an additional descriptor's sink
|
|
52
|
+
or stream (a draining sink and an empty stream for one not set up).
|
|
53
|
+
- ``unref``: stop the scope from killing the process when it closes;
|
|
54
|
+
the ``Reref`` it gives back undoes that.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
pid: ProcessId
|
|
58
|
+
exit_code: Effect[ExitCode, PlatformError]
|
|
59
|
+
is_running: Effect[bool, PlatformError]
|
|
60
|
+
kill: Kill
|
|
61
|
+
stdin: Sink[None, bytes, Never, PlatformError]
|
|
62
|
+
stdout: Stream[bytes, PlatformError]
|
|
63
|
+
stderr: Stream[bytes, PlatformError]
|
|
64
|
+
all: Stream[bytes, PlatformError]
|
|
65
|
+
get_input_fd: Callable[[int], Sink[None, bytes, Never, PlatformError]]
|
|
66
|
+
get_output_fd: Callable[[int], Stream[bytes, PlatformError]]
|
|
67
|
+
unref: Effect[Reref, PlatformError]
|
|
68
|
+
|
|
69
|
+
def __post_init__(self) -> None:
|
|
70
|
+
for name in ("exit_code", "is_running", "unref"):
|
|
71
|
+
require_effect(getattr(self, name), name)
|
|
72
|
+
require_sink(self.stdin, "stdin")
|
|
73
|
+
for name in ("stdout", "stderr", "all"):
|
|
74
|
+
require_stream(getattr(self, name), name)
|
|
75
|
+
|
|
76
|
+
def __repr__(self) -> str:
|
|
77
|
+
return f"ChildProcessHandle(pid={self.pid})"
|