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,77 @@
|
|
|
1
|
+
"""Persistence (Effect's ``effect/persistence``): key-value stores,
|
|
2
|
+
requests whose outcomes are stored, and caches over them.
|
|
3
|
+
|
|
4
|
+
- ``key_value_store``: ``KeyValueStore``, a string-keyed store of text and
|
|
5
|
+
bytes in memory, files, SQL or any text mapping, and schema-typed stores.
|
|
6
|
+
- ``persistable``: ``Persistable`` requests, which carry the schemas of
|
|
7
|
+
their success and error and a ``primary_key()``.
|
|
8
|
+
- ``persistence``: ``Persistence``, request outcomes stored by those
|
|
9
|
+
schemas in memory, a ``KeyValueStore`` or SQL.
|
|
10
|
+
- ``persisted_cache``: ``PersistedCache``, a ``Cache`` in front of a store.
|
|
11
|
+
- ``persisted_queue``: ``PersistedQueue``, durable at-least-once queues in
|
|
12
|
+
memory, SQL or Redis.
|
|
13
|
+
- ``user_directories``: ``UserDirectories``, where a program keeps its
|
|
14
|
+
files per user.
|
|
15
|
+
- ``rate_limiter``: ``RateLimiter``, limits per key by fixed window or
|
|
16
|
+
token bucket, and adaptive limits learned from ``429`` responses.
|
|
17
|
+
- ``circuit_breaker``: ``CircuitBreaker``, stops calling a key that keeps
|
|
18
|
+
failing.
|
|
19
|
+
- ``redis``: the ``Redis`` service the Redis stores run on, made over a
|
|
20
|
+
client by ``redis.make``; ``redis_py`` (extra ``pyfect-dev[redis]``, imported
|
|
21
|
+
on its own) provides it on redis-py.
|
|
22
|
+
|
|
23
|
+
``RequestResolver.persisted`` stores a resolver's outcomes.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from pyfect._public import publish
|
|
27
|
+
from pyfect.persistence import (
|
|
28
|
+
circuit_breaker,
|
|
29
|
+
key_value_store,
|
|
30
|
+
persistable,
|
|
31
|
+
persisted_cache,
|
|
32
|
+
persisted_queue,
|
|
33
|
+
persistence,
|
|
34
|
+
rate_limiter,
|
|
35
|
+
redis,
|
|
36
|
+
user_directories,
|
|
37
|
+
)
|
|
38
|
+
from pyfect.persistence._error import PersistenceError
|
|
39
|
+
from pyfect.persistence.circuit_breaker import CircuitBreaker, CircuitBreakerError
|
|
40
|
+
from pyfect.persistence.key_value_store import KeyValueStore, KeyValueStoreError
|
|
41
|
+
from pyfect.persistence.persistable import Persistable
|
|
42
|
+
from pyfect.persistence.persisted_cache import PersistedCache
|
|
43
|
+
from pyfect.persistence.persisted_queue import PersistedQueue, PersistedQueueError
|
|
44
|
+
from pyfect.persistence.persistence import BackingPersistence, Persistence
|
|
45
|
+
from pyfect.persistence.rate_limiter import RateLimiter, RateLimiterError
|
|
46
|
+
from pyfect.persistence.redis import Redis, RedisError
|
|
47
|
+
from pyfect.persistence.user_directories import UserDirectories
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
"BackingPersistence",
|
|
51
|
+
"CircuitBreaker",
|
|
52
|
+
"CircuitBreakerError",
|
|
53
|
+
"KeyValueStore",
|
|
54
|
+
"KeyValueStoreError",
|
|
55
|
+
"Persistable",
|
|
56
|
+
"PersistedCache",
|
|
57
|
+
"PersistedQueue",
|
|
58
|
+
"PersistedQueueError",
|
|
59
|
+
"Persistence",
|
|
60
|
+
"PersistenceError",
|
|
61
|
+
"RateLimiter",
|
|
62
|
+
"RateLimiterError",
|
|
63
|
+
"Redis",
|
|
64
|
+
"RedisError",
|
|
65
|
+
"UserDirectories",
|
|
66
|
+
"circuit_breaker",
|
|
67
|
+
"key_value_store",
|
|
68
|
+
"persistable",
|
|
69
|
+
"persisted_cache",
|
|
70
|
+
"persisted_queue",
|
|
71
|
+
"persistence",
|
|
72
|
+
"rate_limiter",
|
|
73
|
+
"redis",
|
|
74
|
+
"user_directories",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
publish(globals())
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Reading the columns the SQL stores select: drivers type a row's values
|
|
2
|
+
as ``object``."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import cast
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def column_int(value: object) -> int:
|
|
10
|
+
"""A count or a small integer column (some drivers give ``Decimal`` or
|
|
11
|
+
text for ``COUNT(*)``)."""
|
|
12
|
+
return value if isinstance(value, int) else int(str(value))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def column_bytes(value: object) -> bytes:
|
|
16
|
+
"""A ``BLOB`` / ``BYTEA`` column."""
|
|
17
|
+
match value:
|
|
18
|
+
case bytes():
|
|
19
|
+
return value
|
|
20
|
+
case bytearray() | memoryview():
|
|
21
|
+
return bytes(cast("bytearray", value))
|
|
22
|
+
case _:
|
|
23
|
+
raise TypeError(f"expected a binary column value, got {type(value).__name__}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def column_text(value: object) -> str:
|
|
27
|
+
"""A text column, or a ``VARBINARY`` one holding UTF-8 (a MySQL key)."""
|
|
28
|
+
match value:
|
|
29
|
+
case bytes() | bytearray() | memoryview():
|
|
30
|
+
return bytes(cast("bytes", value)).decode()
|
|
31
|
+
case _:
|
|
32
|
+
return str(value)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""``PersistenceError``: a persistence store failed."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pyfect import schema as S
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PersistenceError(S.TaggedError, identifier="effect/persistence/Persistence/PersistenceError"):
|
|
9
|
+
"""A persistence store could not read or write; ``cause`` is what its
|
|
10
|
+
backend raised. A schema class, so a persisted request may fail with
|
|
11
|
+
it and have the failure stored."""
|
|
12
|
+
|
|
13
|
+
message: str
|
|
14
|
+
cause: object = S.field(S.Union(S.Null, S.Defect()), default=None)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Effect's millisecond ``Duration`` arithmetic, in whole nanoseconds as
|
|
2
|
+
Effect's is, and ``SECONDS_FROM_MILLIS``: a duration in ``Seconds``
|
|
3
|
+
written as milliseconds (Effect's ``Schema.DurationFromMillis``)."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import math
|
|
8
|
+
from typing import TypeIs
|
|
9
|
+
|
|
10
|
+
from pyfect import _js as js
|
|
11
|
+
from pyfect import schema as S
|
|
12
|
+
from pyfect.duration import Seconds, to_millis
|
|
13
|
+
from pyfect.schema import transformation as T
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _is_seconds(value: object) -> TypeIs[Seconds]:
|
|
17
|
+
return isinstance(value, Seconds)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _round_away(value: float) -> int:
|
|
21
|
+
return int(math.copysign(js.round(abs(value)), value))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def whole_nanos(millis: float) -> float:
|
|
25
|
+
"""``millis`` rounded to whole nanoseconds, ties away from zero, as
|
|
26
|
+
Effect's ``Duration`` keeps a fractional millisecond count."""
|
|
27
|
+
if not math.isfinite(millis) or float(millis).is_integer():
|
|
28
|
+
return millis
|
|
29
|
+
return _round_away(millis * 1_000_000) / 1_000_000
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def millis(value: float) -> Seconds:
|
|
33
|
+
"""``Seconds`` of ``value`` milliseconds in whole nanoseconds (Effect's
|
|
34
|
+
``Duration.millis``); NaN is zero."""
|
|
35
|
+
if not math.isfinite(value) or float(value).is_integer():
|
|
36
|
+
return Seconds(value / 1000)
|
|
37
|
+
return Seconds(_round_away(value * 1_000_000) / 1_000_000_000)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def divide_millis(millis: float, by: float) -> float:
|
|
41
|
+
"""``millis`` divided by ``by`` in whole nanoseconds, as Effect's
|
|
42
|
+
``Duration.divideUnsafe``: a non-finite ``by`` gives zero, a zero one
|
|
43
|
+
an infinity. A duration of a fractional millisecond count is held in
|
|
44
|
+
nanoseconds there, divided as an integer (truncated); Effect gives zero
|
|
45
|
+
when ``by`` is not an integer too, which would turn a limit off, so
|
|
46
|
+
that case rounds instead."""
|
|
47
|
+
if not math.isfinite(by):
|
|
48
|
+
return 0.0
|
|
49
|
+
if math.isinf(millis):
|
|
50
|
+
return 0.0 if by == 0 else millis * math.copysign(1.0, by)
|
|
51
|
+
if float(millis).is_integer():
|
|
52
|
+
quotient = js.divide(millis, by)
|
|
53
|
+
return 0.0 if math.isnan(quotient) else whole_nanos(quotient)
|
|
54
|
+
nanos = _round_away(millis * 1_000_000)
|
|
55
|
+
if by == 0:
|
|
56
|
+
return math.copysign(math.inf, nanos) * math.copysign(1.0, by)
|
|
57
|
+
if not float(by).is_integer():
|
|
58
|
+
return _round_away(nanos / by) / 1_000_000
|
|
59
|
+
quotient = abs(nanos) // abs(int(by))
|
|
60
|
+
return quotient * (1 if (nanos < 0) == (by < 0) else -1) / 1_000_000
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
SECONDS_FROM_MILLIS = S.Finite.pipe(
|
|
64
|
+
S.decode_to(
|
|
65
|
+
S.declare(_is_seconds, expected="Seconds"),
|
|
66
|
+
T.transform(decode=millis, encode=to_millis),
|
|
67
|
+
)
|
|
68
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""The string keys of the SQL stores on MySQL and SQL Server, compared
|
|
2
|
+
exactly.
|
|
3
|
+
|
|
4
|
+
Both servers compare strings under the column's collation. Their usual
|
|
5
|
+
defaults (``utf8mb4_0900_ai_ci``, ``*_CI_AS``) ignore case (and on MySQL
|
|
6
|
+
accents), and every SQL Server collation, like MySQL's ``PAD SPACE`` ones
|
|
7
|
+
(``utf8mb4_bin`` among them), ignores trailing spaces in ``=``: ``K:Alice``,
|
|
8
|
+
``K:alice`` and ``K:alice `` would be one key. On MySQL a key is
|
|
9
|
+
``VARBINARY``, whose bytes all count. On SQL Server it is a ``BIN2``
|
|
10
|
+
``NVARCHAR`` compared with ``key_equals`` / ``mssql_match``, which also
|
|
11
|
+
compare lengths; a write whose key differs from a stored one only in
|
|
12
|
+
trailing spaces then fails on the primary key instead of replacing it.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Final, LiteralString
|
|
18
|
+
|
|
19
|
+
from pyfect.sql.client import SqlClientService
|
|
20
|
+
from pyfect.sql.statement import Fragment
|
|
21
|
+
|
|
22
|
+
MYSQL_KEY: Final = "VARBINARY(764)"
|
|
23
|
+
"""191 four-byte characters, as the ``VARCHAR(191)`` it replaces."""
|
|
24
|
+
MSSQL_KEY: Final = "NVARCHAR(450) COLLATE Latin1_General_100_BIN2"
|
|
25
|
+
MSSQL_PAIR_KEY: Final = "NVARCHAR(191) COLLATE Latin1_General_100_BIN2"
|
|
26
|
+
"""Each half of a two-column key (the index key stays under 900 bytes)."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _same_length(column: LiteralString, value: LiteralString) -> LiteralString:
|
|
30
|
+
return f"DATALENGTH({column}) = DATALENGTH(CAST({value} AS NVARCHAR(4000)))"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def key_equals(sql: SqlClientService, column: str, key: str) -> Fragment:
|
|
34
|
+
"""``column = key``, counting every character on every dialect."""
|
|
35
|
+
name = sql.identifier(column)
|
|
36
|
+
return sql.on_dialect_or_else(
|
|
37
|
+
mssql=lambda: sql(f"({{}} = {{}} AND {_same_length('{}', '{}')})", name, key, name, key),
|
|
38
|
+
or_else=lambda: sql("{} = {}", name, key),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def mssql_match(column: LiteralString) -> LiteralString:
|
|
43
|
+
"""The ``MERGE ... ON`` condition matching ``target.column`` to
|
|
44
|
+
``source.column`` exactly."""
|
|
45
|
+
target = f"target.{column}"
|
|
46
|
+
source = f"source.{column}"
|
|
47
|
+
return f"{target} = {source} AND {_same_length(target, source)}"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Circuit breakers per key (Effect's ``effect/persistence/CircuitBreaker``)::
|
|
2
|
+
|
|
3
|
+
breaker = yield from circuit_breaker.make(
|
|
4
|
+
trip_policy=circuit_breaker.consecutive(5), open_duration=30
|
|
5
|
+
)
|
|
6
|
+
yield from call_api.pipe(breaker.execute(key="api"))
|
|
7
|
+
|
|
8
|
+
A key's circuit is ``Closed`` (calls run and are counted), ``Open`` (calls
|
|
9
|
+
fail with ``CircuitOpen`` for the open time) or ``HalfOpen`` (one probe
|
|
10
|
+
runs; its success closes the circuit, its failure reopens it). The trip
|
|
11
|
+
policy is ``consecutive`` failures or a failure ``rate``; ``classify``
|
|
12
|
+
decides how a call counts (``success``, ``ignored``, ``failure()``).
|
|
13
|
+
|
|
14
|
+
States live in a ``CircuitBreakerStore``: ``layer_store_memory``, or
|
|
15
|
+
``layer_store_redis`` (shared by every process using that Redis).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from pyfect._public import publish
|
|
19
|
+
from pyfect.persistence.circuit_breaker._breaker import (
|
|
20
|
+
CircuitBreaker,
|
|
21
|
+
CircuitBreakerService,
|
|
22
|
+
Classify,
|
|
23
|
+
Executor,
|
|
24
|
+
OnStoreError,
|
|
25
|
+
OpenDuration,
|
|
26
|
+
layer,
|
|
27
|
+
make,
|
|
28
|
+
)
|
|
29
|
+
from pyfect.persistence.circuit_breaker._errors import (
|
|
30
|
+
CIRCUIT_BREAKER_ERROR_REASON,
|
|
31
|
+
CircuitBreakerError,
|
|
32
|
+
CircuitBreakerErrorReason,
|
|
33
|
+
CircuitBreakerStoreError,
|
|
34
|
+
CircuitHalfOpen,
|
|
35
|
+
CircuitOpen,
|
|
36
|
+
)
|
|
37
|
+
from pyfect.persistence.circuit_breaker._memory import layer_store_memory, make_store_memory
|
|
38
|
+
from pyfect.persistence.circuit_breaker._redis import (
|
|
39
|
+
layer_store_redis,
|
|
40
|
+
layer_store_redis_config,
|
|
41
|
+
make_store_redis,
|
|
42
|
+
)
|
|
43
|
+
from pyfect.persistence.circuit_breaker._store import (
|
|
44
|
+
CircuitBreakerStore,
|
|
45
|
+
CircuitBreakerStoreService,
|
|
46
|
+
Record,
|
|
47
|
+
)
|
|
48
|
+
from pyfect.persistence.circuit_breaker._types import (
|
|
49
|
+
Admission,
|
|
50
|
+
Closed,
|
|
51
|
+
CloseTransition,
|
|
52
|
+
Consecutive,
|
|
53
|
+
FailureOutcome,
|
|
54
|
+
HalfOpen,
|
|
55
|
+
HalfOpenTransition,
|
|
56
|
+
IgnoredOutcome,
|
|
57
|
+
Open,
|
|
58
|
+
OpenTransition,
|
|
59
|
+
Outcome,
|
|
60
|
+
OutcomeKind,
|
|
61
|
+
Rate,
|
|
62
|
+
State,
|
|
63
|
+
SuccessOutcome,
|
|
64
|
+
Transition,
|
|
65
|
+
TripPolicy,
|
|
66
|
+
consecutive,
|
|
67
|
+
failure,
|
|
68
|
+
ignored,
|
|
69
|
+
rate,
|
|
70
|
+
success,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
"CIRCUIT_BREAKER_ERROR_REASON",
|
|
75
|
+
"Admission",
|
|
76
|
+
"CircuitBreaker",
|
|
77
|
+
"CircuitBreakerError",
|
|
78
|
+
"CircuitBreakerErrorReason",
|
|
79
|
+
"CircuitBreakerService",
|
|
80
|
+
"CircuitBreakerStore",
|
|
81
|
+
"CircuitBreakerStoreError",
|
|
82
|
+
"CircuitBreakerStoreService",
|
|
83
|
+
"CircuitHalfOpen",
|
|
84
|
+
"CircuitOpen",
|
|
85
|
+
"Classify",
|
|
86
|
+
"CloseTransition",
|
|
87
|
+
"Closed",
|
|
88
|
+
"Consecutive",
|
|
89
|
+
"Executor",
|
|
90
|
+
"FailureOutcome",
|
|
91
|
+
"HalfOpen",
|
|
92
|
+
"HalfOpenTransition",
|
|
93
|
+
"IgnoredOutcome",
|
|
94
|
+
"OnStoreError",
|
|
95
|
+
"Open",
|
|
96
|
+
"OpenDuration",
|
|
97
|
+
"OpenTransition",
|
|
98
|
+
"Outcome",
|
|
99
|
+
"OutcomeKind",
|
|
100
|
+
"Rate",
|
|
101
|
+
"Record",
|
|
102
|
+
"State",
|
|
103
|
+
"SuccessOutcome",
|
|
104
|
+
"Transition",
|
|
105
|
+
"TripPolicy",
|
|
106
|
+
"consecutive",
|
|
107
|
+
"failure",
|
|
108
|
+
"ignored",
|
|
109
|
+
"layer",
|
|
110
|
+
"layer_store_memory",
|
|
111
|
+
"layer_store_redis",
|
|
112
|
+
"layer_store_redis_config",
|
|
113
|
+
"make",
|
|
114
|
+
"make_store_memory",
|
|
115
|
+
"make_store_redis",
|
|
116
|
+
"rate",
|
|
117
|
+
"success",
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
publish(globals())
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""``CircuitBreaker``: stops calling a key that keeps failing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Generator
|
|
6
|
+
from typing import Any, Literal, Never, overload
|
|
7
|
+
|
|
8
|
+
from pyfect._core import Effect
|
|
9
|
+
from pyfect._effect_args import require_effect
|
|
10
|
+
from pyfect._primitives import Restore, uninterruptible_mask
|
|
11
|
+
from pyfect.cause import Cause, Fail
|
|
12
|
+
from pyfect.clock import ClockService, clock_with
|
|
13
|
+
from pyfect.context import Tag
|
|
14
|
+
from pyfect.duration import ZERO, Duration, Seconds, to_seconds
|
|
15
|
+
from pyfect.effect import fail_cause, gen, provide_service, succeed, suspend, void
|
|
16
|
+
from pyfect.exit import Exit, Failure, Success
|
|
17
|
+
from pyfect.exit import succeed as exit_succeed
|
|
18
|
+
from pyfect.layer import Layer
|
|
19
|
+
from pyfect.layer import effect as layer_effect
|
|
20
|
+
from pyfect.logger._log import log_unhandled
|
|
21
|
+
from pyfect.persistence.circuit_breaker._errors import (
|
|
22
|
+
CircuitBreakerError,
|
|
23
|
+
CircuitBreakerStoreError,
|
|
24
|
+
)
|
|
25
|
+
from pyfect.persistence.circuit_breaker._store import (
|
|
26
|
+
CircuitBreakerStore,
|
|
27
|
+
CircuitBreakerStoreService,
|
|
28
|
+
)
|
|
29
|
+
from pyfect.persistence.circuit_breaker._types import (
|
|
30
|
+
Admission,
|
|
31
|
+
Consecutive,
|
|
32
|
+
FailureOutcome,
|
|
33
|
+
Outcome,
|
|
34
|
+
State,
|
|
35
|
+
SuccessOutcome,
|
|
36
|
+
Transition,
|
|
37
|
+
TripPolicy,
|
|
38
|
+
failure,
|
|
39
|
+
ignored,
|
|
40
|
+
success,
|
|
41
|
+
)
|
|
42
|
+
from pyfect.pubsub import PubSub
|
|
43
|
+
from pyfect.result import Ok
|
|
44
|
+
from pyfect.schedule import Schedule, Stop, to_step
|
|
45
|
+
from pyfect.scope import CloseableScope, Scope, add_finalizer, scoped_with
|
|
46
|
+
from pyfect.stream import Stream, from_pubsub_take
|
|
47
|
+
from pyfect.take import Take
|
|
48
|
+
|
|
49
|
+
type OnStoreError = Literal["allow", "fail"]
|
|
50
|
+
"""When the store cannot admit a call: ``"allow"`` runs it unrecorded
|
|
51
|
+
(reporting the cause at the ``UnhandledLogLevel``), ``"fail"`` fails with
|
|
52
|
+
the ``CircuitBreakerStoreError``."""
|
|
53
|
+
|
|
54
|
+
type OpenDuration = Duration | Schedule[object, int]
|
|
55
|
+
"""How long an opening lasts: a duration, or a schedule replayed with the
|
|
56
|
+
number of consecutive openings (its delays measured from the opening)."""
|
|
57
|
+
|
|
58
|
+
type Classify[A, E, R = Never] = Callable[[Exit[A, E]], Outcome | Effect[Outcome, Never, R]]
|
|
59
|
+
"""How a call's exit counts; without it a success is a ``success`` and a
|
|
60
|
+
failure a ``failure()``. An interrupt-only exit is always ``ignored``."""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def recovering(message: str) -> Callable[[Cause[Any]], Effect[None]]:
|
|
64
|
+
"""Reports a recoverable failure at the ``UnhandledLogLevel``; a cause
|
|
65
|
+
holding a defect keeps failing."""
|
|
66
|
+
|
|
67
|
+
def handle(cause: Cause[Any]) -> Effect[None]:
|
|
68
|
+
found = cause.find_recoverable_error()
|
|
69
|
+
return log_unhandled(message, cause) if isinstance(found, Fail) else fail_cause(found)
|
|
70
|
+
|
|
71
|
+
return handle
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _open_duration(open_duration: OpenDuration) -> Callable[[int], Effect[Seconds]]:
|
|
75
|
+
if not isinstance(open_duration, Schedule):
|
|
76
|
+
fixed: Effect[Seconds] = succeed(to_seconds(open_duration))
|
|
77
|
+
return lambda _: fixed
|
|
78
|
+
schedule = open_duration
|
|
79
|
+
|
|
80
|
+
# Replays the schedule on a simulated clock, so ``up_to`` measures the
|
|
81
|
+
# time spent open rather than wall time.
|
|
82
|
+
def replay(openings: int) -> Effect[Seconds]:
|
|
83
|
+
def body(clock: ClockService) -> Generator[Effect[Any], Any, Seconds]:
|
|
84
|
+
step = yield from to_step(schedule)
|
|
85
|
+
now = clock.now_unsafe()
|
|
86
|
+
delay = ZERO
|
|
87
|
+
for attempt in range(1, openings + 1):
|
|
88
|
+
decision = yield from step(now, attempt)
|
|
89
|
+
if isinstance(decision, Stop):
|
|
90
|
+
break
|
|
91
|
+
delay = decision.delay
|
|
92
|
+
now += delay
|
|
93
|
+
return delay
|
|
94
|
+
|
|
95
|
+
return clock_with(lambda clock: gen(lambda: body(clock)))
|
|
96
|
+
|
|
97
|
+
return replay
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _classify[A, E, R](
|
|
101
|
+
exit: Exit[A, E], classify: Classify[A, E, R] | None, restore: Restore
|
|
102
|
+
) -> Effect[Exit[Outcome, Never], Never, R]:
|
|
103
|
+
if isinstance(exit, Failure) and exit.cause.has_interrupts_only:
|
|
104
|
+
return succeed(exit_succeed(ignored))
|
|
105
|
+
if classify is None:
|
|
106
|
+
return succeed(exit_succeed(success if isinstance(exit, Success) else failure()))
|
|
107
|
+
run = classify
|
|
108
|
+
|
|
109
|
+
def call() -> Effect[Outcome, Never, R]:
|
|
110
|
+
outcome = run(exit)
|
|
111
|
+
return outcome if isinstance(outcome, Effect) else succeed(outcome)
|
|
112
|
+
|
|
113
|
+
return restore(suspend(call)).exit()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class CircuitBreakerService:
|
|
117
|
+
"""Runs calls per key through a circuit (see ``make``)."""
|
|
118
|
+
|
|
119
|
+
__slots__ = ("_max_open", "_on_store_error", "_open_duration", "_policy", "_pubsub", "_store")
|
|
120
|
+
|
|
121
|
+
def __init__( # noqa: PLR0917
|
|
122
|
+
self,
|
|
123
|
+
store: CircuitBreakerStoreService,
|
|
124
|
+
pubsub: PubSub[Take[Transition]],
|
|
125
|
+
policy: TripPolicy,
|
|
126
|
+
open_duration: OpenDuration,
|
|
127
|
+
max_open_duration: Seconds,
|
|
128
|
+
on_store_error: OnStoreError,
|
|
129
|
+
) -> None:
|
|
130
|
+
self._store = store
|
|
131
|
+
self._pubsub = pubsub
|
|
132
|
+
self._policy = policy
|
|
133
|
+
self._open_duration = _open_duration(open_duration)
|
|
134
|
+
self._max_open = max_open_duration
|
|
135
|
+
self._on_store_error = on_store_error
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def transitions(self) -> Stream[Transition]:
|
|
139
|
+
"""Every transition from now on; it ends when the breaker's scope
|
|
140
|
+
closes. A slow reader loses the oldest past 128."""
|
|
141
|
+
return from_pubsub_take(self._pubsub)
|
|
142
|
+
|
|
143
|
+
def state(self, key: str) -> Effect[State, CircuitBreakerError]:
|
|
144
|
+
"""``key``'s state now."""
|
|
145
|
+
return self._store.state(key)
|
|
146
|
+
|
|
147
|
+
@overload
|
|
148
|
+
def execute[R2 = Never](
|
|
149
|
+
self, effect: None = None, /, *, key: str, classify: Classify[Any, Any, R2] | None = None
|
|
150
|
+
) -> Executor[R2]: ...
|
|
151
|
+
@overload
|
|
152
|
+
def execute[A, E, R, R2 = Never](
|
|
153
|
+
self,
|
|
154
|
+
effect: Effect[A, E, R],
|
|
155
|
+
/,
|
|
156
|
+
*,
|
|
157
|
+
key: str,
|
|
158
|
+
classify: Classify[A, E, R2] | None = None,
|
|
159
|
+
) -> Effect[A, E | CircuitBreakerError, R | R2]: ...
|
|
160
|
+
def execute(
|
|
161
|
+
self,
|
|
162
|
+
effect: Effect[Any, Any, Any] | None = None,
|
|
163
|
+
/,
|
|
164
|
+
*,
|
|
165
|
+
key: str,
|
|
166
|
+
classify: Classify[Any, Any, Any] | None = None,
|
|
167
|
+
) -> Executor[Any] | Effect[Any, Any, Any]:
|
|
168
|
+
"""Run ``effect`` through ``key``'s circuit: rejected with
|
|
169
|
+
``CircuitOpen`` while it is open and ``CircuitHalfOpen`` while its
|
|
170
|
+
probe runs, else run and counted by ``classify``. Without
|
|
171
|
+
``effect``, the function doing that for ``.pipe``."""
|
|
172
|
+
if effect is None:
|
|
173
|
+
return Executor(self, key, classify)
|
|
174
|
+
require_effect(effect, "effect")
|
|
175
|
+
return self._run(effect, key, classify)
|
|
176
|
+
|
|
177
|
+
def _publish(self, transition: Transition | None) -> Effect[None]:
|
|
178
|
+
return void if transition is None else self._pubsub.publish((transition,)).as_void()
|
|
179
|
+
|
|
180
|
+
def _open_for(self, outcome: Outcome, admission: Admission) -> Effect[Seconds]:
|
|
181
|
+
# Only an outcome that can open the circuit needs the open time.
|
|
182
|
+
if not isinstance(outcome, FailureOutcome) and (
|
|
183
|
+
not isinstance(outcome, SuccessOutcome)
|
|
184
|
+
or admission.probe
|
|
185
|
+
or isinstance(self._policy, Consecutive)
|
|
186
|
+
):
|
|
187
|
+
return succeed(ZERO)
|
|
188
|
+
hint = outcome.retry_after if isinstance(outcome, FailureOutcome) else None
|
|
189
|
+
max_open = self._max_open
|
|
190
|
+
|
|
191
|
+
def hinted(delay: Seconds) -> Seconds:
|
|
192
|
+
return delay if hint is None else max(delay, min(hint, max_open))
|
|
193
|
+
|
|
194
|
+
return self._open_duration(admission.consecutive_openings + 1).map(hinted)
|
|
195
|
+
|
|
196
|
+
def _run[A, E, R, R2](
|
|
197
|
+
self, effect: Effect[A, E, R], key: str, classify: Classify[A, E, R2] | None
|
|
198
|
+
) -> Effect[A, E | CircuitBreakerError, R | R2]:
|
|
199
|
+
store = self._store
|
|
200
|
+
|
|
201
|
+
def admitted(
|
|
202
|
+
restore: Restore, scope: CloseableScope
|
|
203
|
+
) -> Generator[Effect[Any, Any, Any], Any, A]:
|
|
204
|
+
admit = yield from store.admit(key).pipe(provide_service(Scope, scope)).exit()
|
|
205
|
+
if isinstance(admit, Failure):
|
|
206
|
+
error = admit.cause.find_error()
|
|
207
|
+
if (
|
|
208
|
+
self._on_store_error == "fail"
|
|
209
|
+
or not isinstance(error, Ok)
|
|
210
|
+
or not isinstance(error.value.reason, CircuitBreakerStoreError)
|
|
211
|
+
):
|
|
212
|
+
return (yield from fail_cause(admit.cause))
|
|
213
|
+
yield from log_unhandled(
|
|
214
|
+
"CircuitBreaker store unavailable, running the call unrecorded", admit.cause
|
|
215
|
+
)
|
|
216
|
+
return (yield from restore(effect))
|
|
217
|
+
admission = admit.value
|
|
218
|
+
yield from self._publish(admission.transition)
|
|
219
|
+
exit = yield from restore(effect).exit()
|
|
220
|
+
classified = yield from _classify(exit, classify, restore)
|
|
221
|
+
outcome = classified.value if isinstance(classified, Success) else ignored
|
|
222
|
+
open_for = yield from self._open_for(outcome, admission)
|
|
223
|
+
yield from (
|
|
224
|
+
store.record(
|
|
225
|
+
key=key,
|
|
226
|
+
generation=admission.generation,
|
|
227
|
+
outcome=outcome.kind,
|
|
228
|
+
trip_policy=self._policy,
|
|
229
|
+
open_for=open_for,
|
|
230
|
+
)
|
|
231
|
+
.flat_map(self._publish)
|
|
232
|
+
.catch_cause(recovering("CircuitBreaker failed to record an outcome"))
|
|
233
|
+
)
|
|
234
|
+
if isinstance(classified, Success):
|
|
235
|
+
if isinstance(exit, Success):
|
|
236
|
+
return exit.value
|
|
237
|
+
return (yield from fail_cause(exit.cause))
|
|
238
|
+
cause: Cause[Any] = (
|
|
239
|
+
exit.cause + classified.cause if isinstance(exit, Failure) else classified.cause
|
|
240
|
+
)
|
|
241
|
+
return (yield from fail_cause(cause))
|
|
242
|
+
|
|
243
|
+
return uninterruptible_mask(
|
|
244
|
+
lambda restore: scoped_with(lambda scope: gen(lambda: admitted(restore, scope)))
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class Executor[R2 = Never]:
|
|
249
|
+
"""``breaker.execute(key=...)``: runs an effect through the circuit."""
|
|
250
|
+
|
|
251
|
+
__slots__ = ("_breaker", "_classify", "_key")
|
|
252
|
+
|
|
253
|
+
def __init__(
|
|
254
|
+
self,
|
|
255
|
+
breaker: CircuitBreakerService,
|
|
256
|
+
key: str,
|
|
257
|
+
classify: Classify[Any, Any, R2] | None,
|
|
258
|
+
) -> None:
|
|
259
|
+
self._breaker = breaker
|
|
260
|
+
self._key = key
|
|
261
|
+
self._classify = classify
|
|
262
|
+
|
|
263
|
+
def __call__[A, E, R](
|
|
264
|
+
self, effect: Effect[A, E, R]
|
|
265
|
+
) -> Effect[A, E | CircuitBreakerError, R | R2]:
|
|
266
|
+
require_effect(effect, "effect")
|
|
267
|
+
return self._breaker.execute(effect, key=self._key, classify=self._classify)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class CircuitBreaker(Tag[CircuitBreakerService], key="~effect/persistence/CircuitBreaker"):
|
|
271
|
+
"""Stops calling a key that keeps failing."""
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def make(
|
|
275
|
+
*,
|
|
276
|
+
trip_policy: TripPolicy,
|
|
277
|
+
open_duration: OpenDuration,
|
|
278
|
+
max_open_duration: Duration = 3600,
|
|
279
|
+
on_store_error: OnStoreError = "allow",
|
|
280
|
+
) -> Effect[CircuitBreakerService, Never, CircuitBreakerStore | Scope]:
|
|
281
|
+
"""A breaker on the ``CircuitBreakerStore``. A closed key opens when
|
|
282
|
+
``trip_policy`` says; an opening lasts ``open_duration`` (a schedule
|
|
283
|
+
gives longer ones as openings repeat), or a failure's ``retry_after``
|
|
284
|
+
up to ``max_open_duration`` (default an hour). After it, one probe
|
|
285
|
+
runs: its success closes the key, its failure reopens it. A key closed
|
|
286
|
+
as long as its last opening starts counting openings afresh.
|
|
287
|
+
``transitions`` ends when the scope closes."""
|
|
288
|
+
max_open = to_seconds(max_open_duration)
|
|
289
|
+
|
|
290
|
+
def build() -> Generator[Effect[Any, Never, Any], Any, CircuitBreakerService]:
|
|
291
|
+
store = yield from CircuitBreakerStore()
|
|
292
|
+
pubsub: PubSub[Take[Transition]] = yield from PubSub[Take[Transition]].sliding(128)
|
|
293
|
+
yield from add_finalizer(lambda _: pubsub.end(exit_succeed(None)))
|
|
294
|
+
return CircuitBreakerService(
|
|
295
|
+
store, pubsub, trip_policy, open_duration, max_open, on_store_error
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
return gen(build)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def layer(
|
|
302
|
+
*,
|
|
303
|
+
trip_policy: TripPolicy,
|
|
304
|
+
open_duration: OpenDuration,
|
|
305
|
+
max_open_duration: Duration = 3600,
|
|
306
|
+
on_store_error: OnStoreError = "allow",
|
|
307
|
+
) -> Layer[CircuitBreaker, Never, CircuitBreakerStore]:
|
|
308
|
+
"""Provides ``CircuitBreaker`` (see ``make``)."""
|
|
309
|
+
return layer_effect(
|
|
310
|
+
CircuitBreaker,
|
|
311
|
+
make(
|
|
312
|
+
trip_policy=trip_policy,
|
|
313
|
+
open_duration=open_duration,
|
|
314
|
+
max_open_duration=max_open_duration,
|
|
315
|
+
on_store_error=on_store_error,
|
|
316
|
+
),
|
|
317
|
+
)
|