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,334 @@
|
|
|
1
|
+
"""``layer_store_sql``: queues in one SQL table, rows claimed under a
|
|
2
|
+
lock per poll."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import uuid
|
|
7
|
+
from collections.abc import Callable, Generator, Sequence
|
|
8
|
+
from typing import Any, Final, Never
|
|
9
|
+
|
|
10
|
+
from pyfect._concurrency import for_each
|
|
11
|
+
from pyfect._core import Effect
|
|
12
|
+
from pyfect._json_text import PARSE_ERRORS, parse, stringify
|
|
13
|
+
from pyfect.cause import Cause
|
|
14
|
+
from pyfect.cause import die as cause_die
|
|
15
|
+
from pyfect.duration import Duration, Seconds
|
|
16
|
+
from pyfect.effect import fail, gen, succeed, suspend, sync
|
|
17
|
+
from pyfect.exit import Exit, Success
|
|
18
|
+
from pyfect.layer import Layer
|
|
19
|
+
from pyfect.layer import effect as layer_effect
|
|
20
|
+
from pyfect.logger import log_warning
|
|
21
|
+
from pyfect.persistence._columns import column_int
|
|
22
|
+
from pyfect.persistence.persisted_queue._error import PersistedQueueError, dead_letter_from_cause
|
|
23
|
+
from pyfect.persistence.persisted_queue._mailbox import Mailbox, run_poller, take_from
|
|
24
|
+
from pyfect.persistence.persisted_queue._sql_migrations import migrations
|
|
25
|
+
from pyfect.persistence.persisted_queue._sql_statements import (
|
|
26
|
+
CleanupBatch,
|
|
27
|
+
Dialect,
|
|
28
|
+
Element,
|
|
29
|
+
OfferStatement,
|
|
30
|
+
Settlements,
|
|
31
|
+
cleanup_statement,
|
|
32
|
+
offer_statement,
|
|
33
|
+
poll_statement,
|
|
34
|
+
store_text,
|
|
35
|
+
)
|
|
36
|
+
from pyfect.persistence.persisted_queue._store import (
|
|
37
|
+
PersistedQueueStore,
|
|
38
|
+
PersistedQueueStoreService,
|
|
39
|
+
QueueStates,
|
|
40
|
+
TakenElement,
|
|
41
|
+
TakeOptions,
|
|
42
|
+
ack_retry_schedule,
|
|
43
|
+
store_interval,
|
|
44
|
+
)
|
|
45
|
+
from pyfect.persistence.persistence._sql import SQL_CLEANUP_BATCH_SIZE
|
|
46
|
+
from pyfect.rc_map import RcMap
|
|
47
|
+
from pyfect.schedule import fixed, spaced
|
|
48
|
+
from pyfect.scope import Scope, add_finalizer
|
|
49
|
+
from pyfect.sql import migrator
|
|
50
|
+
from pyfect.sql.client import SqlClient
|
|
51
|
+
from pyfect.sql.connection import Row
|
|
52
|
+
from pyfect.sql.error import SqlError
|
|
53
|
+
from pyfect.sql.statement import Fragment
|
|
54
|
+
|
|
55
|
+
_CLEANUP_BATCH_DELAY: Final = 0.01
|
|
56
|
+
_DEFAULT_POLL_INTERVAL: Final = Seconds(1)
|
|
57
|
+
_DEFAULT_LOCK_REFRESH_INTERVAL: Final = Seconds(30)
|
|
58
|
+
_DEFAULT_LOCK_EXPIRATION: Final = Seconds(120)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _offer_failed(cause: Cause[Any]) -> Effect[Never, PersistedQueueError]:
|
|
62
|
+
return fail(
|
|
63
|
+
PersistedQueueError(
|
|
64
|
+
message="Failed to offer element to persisted queue", cause=cause.raisable()
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _cleanup_failed(cause: Cause[Any]) -> Effect[Never, PersistedQueueError]:
|
|
70
|
+
return fail(
|
|
71
|
+
PersistedQueueError(message="Failed to clean up persisted queue", cause=cause.raisable())
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _sequence(value: object) -> int | str:
|
|
76
|
+
return value if isinstance(value, int | str) else str(value)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _lookup( # noqa: PLR0917
|
|
80
|
+
dialect: Dialect,
|
|
81
|
+
table_name: str,
|
|
82
|
+
expires_at: Fragment,
|
|
83
|
+
poll_every: Seconds,
|
|
84
|
+
lock_refresh: Seconds,
|
|
85
|
+
states: QueueStates,
|
|
86
|
+
settlements: Settlements,
|
|
87
|
+
) -> Callable[[str], Effect[Mailbox[Element], Never, Scope]]:
|
|
88
|
+
"""The mailbox of queue ``name``: its poller, and a fiber that marks
|
|
89
|
+
failed every lock refresh interval the rows whose worker crashed on
|
|
90
|
+
their last attempt. Closing it releases the claims it holds."""
|
|
91
|
+
sql = dialect.sql
|
|
92
|
+
claim = poll_statement(dialect, table_name, expires_at)
|
|
93
|
+
|
|
94
|
+
def lookup(name: str) -> Effect[Mailbox[Element], Never, Scope]:
|
|
95
|
+
state = states.get(name)
|
|
96
|
+
max_attempts = state.max_attempts
|
|
97
|
+
mailbox = Mailbox[Element]()
|
|
98
|
+
|
|
99
|
+
# no finalizer will ever settle these rows
|
|
100
|
+
flip_exhausted = sql(
|
|
101
|
+
"UPDATE {} SET state = 'failed', acquired_at = NULL, acquired_by = NULL, "
|
|
102
|
+
"updated_at = {}, "
|
|
103
|
+
"last_failure = COALESCE(last_failure, 'Lock expired after final attempt') "
|
|
104
|
+
"WHERE queue_name = {} AND state = 'pending' AND attempts >= {} "
|
|
105
|
+
"AND (acquired_at IS NULL OR acquired_at < {})",
|
|
106
|
+
sql.identifier(table_name),
|
|
107
|
+
dialect.now,
|
|
108
|
+
name,
|
|
109
|
+
max_attempts,
|
|
110
|
+
expires_at,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def poll(size: int) -> Effect[list[Element], SqlError]:
|
|
114
|
+
lock = str(uuid.uuid4())
|
|
115
|
+
|
|
116
|
+
def claimed(rows: Sequence[Row]) -> list[Element]:
|
|
117
|
+
elements = [
|
|
118
|
+
Element(
|
|
119
|
+
sequence=_sequence(row["sequence"]),
|
|
120
|
+
id=str(row["id"]),
|
|
121
|
+
element=str(row["element"]),
|
|
122
|
+
attempts=column_int(row["attempts"]),
|
|
123
|
+
lock=lock,
|
|
124
|
+
)
|
|
125
|
+
for row in rows
|
|
126
|
+
]
|
|
127
|
+
settlements.held.update(dict.fromkeys(elements))
|
|
128
|
+
return elements
|
|
129
|
+
|
|
130
|
+
return claim(name, max_attempts, size, dialect.string_literal(lock)).map(claimed)
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
add_finalizer(
|
|
134
|
+
lambda _: mailbox.queue.clear().flat_map(
|
|
135
|
+
lambda elements: for_each(
|
|
136
|
+
elements, settlements.interrupt, concurrency="unbounded"
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
.and_then(
|
|
141
|
+
flip_exhausted.tap_cause(log_warning)
|
|
142
|
+
.ignore()
|
|
143
|
+
.schedule(spaced(lock_refresh))
|
|
144
|
+
.interruptible()
|
|
145
|
+
.fork_scoped()
|
|
146
|
+
)
|
|
147
|
+
.and_then(run_poller(mailbox, poll=poll, nudge=state.nudge, poll_interval=poll_every))
|
|
148
|
+
.as_(mailbox)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return lookup
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class _SqlStore:
|
|
155
|
+
__slots__ = ("_cleanup_batch", "_dialect", "_mailboxes", "_offer", "_settlements", "_states")
|
|
156
|
+
|
|
157
|
+
def __init__( # noqa: PLR0917
|
|
158
|
+
self,
|
|
159
|
+
dialect: Dialect,
|
|
160
|
+
offer: OfferStatement,
|
|
161
|
+
cleanup_batch: CleanupBatch,
|
|
162
|
+
settlements: Settlements,
|
|
163
|
+
states: QueueStates,
|
|
164
|
+
mailboxes: RcMap[str, Mailbox[Element], Never],
|
|
165
|
+
) -> None:
|
|
166
|
+
self._dialect = dialect
|
|
167
|
+
self._offer = offer
|
|
168
|
+
self._cleanup_batch = cleanup_batch
|
|
169
|
+
self._settlements = settlements
|
|
170
|
+
self._states = states
|
|
171
|
+
self._mailboxes = mailboxes
|
|
172
|
+
|
|
173
|
+
def offer(
|
|
174
|
+
self,
|
|
175
|
+
*,
|
|
176
|
+
name: str,
|
|
177
|
+
id: str,
|
|
178
|
+
element: object,
|
|
179
|
+
is_custom_id: bool,
|
|
180
|
+
delay: Seconds | None = None,
|
|
181
|
+
) -> Effect[None, PersistedQueueError]:
|
|
182
|
+
dialect = self._dialect
|
|
183
|
+
|
|
184
|
+
def send() -> Effect[object, SqlError]:
|
|
185
|
+
visible_at = dialect.now if delay is None else dialect.delayed_visible_at(delay)
|
|
186
|
+
return self._offer(store_text(id), name, stringify(element), visible_at)
|
|
187
|
+
|
|
188
|
+
def nudge() -> None:
|
|
189
|
+
state = self._states.peek(name)
|
|
190
|
+
if state is not None:
|
|
191
|
+
state.nudge.open_unsafe()
|
|
192
|
+
|
|
193
|
+
# wake takers once the offer is committed
|
|
194
|
+
return (
|
|
195
|
+
suspend(send)
|
|
196
|
+
.catch_cause(_offer_failed)
|
|
197
|
+
.flat_map(lambda _: dialect.sql.after_commit(sync(nudge)))
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def _settle(
|
|
201
|
+
self, options: TakeOptions, element: Element, exit_: Exit[Any, Any]
|
|
202
|
+
) -> Effect[None]:
|
|
203
|
+
settlements = self._settlements
|
|
204
|
+
if isinstance(exit_, Success):
|
|
205
|
+
return settlements.complete(element)
|
|
206
|
+
dead = dead_letter_from_cause(exit_.cause)
|
|
207
|
+
if dead is not None:
|
|
208
|
+
return settlements.fail(element, dead.cause)
|
|
209
|
+
if exit_.cause.has_interrupts_only:
|
|
210
|
+
return settlements.interrupt(element)
|
|
211
|
+
if element.attempts >= options.max_attempts:
|
|
212
|
+
return settlements.fail(element, exit_.cause)
|
|
213
|
+
return options.retry_delay(element.attempts).flat_map(
|
|
214
|
+
lambda delay: settlements.retry(element, delay, exit_.cause)
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
def take(self, options: TakeOptions) -> Effect[TakenElement, PersistedQueueError, Scope]:
|
|
218
|
+
def claimed(element: Element) -> Effect[TakenElement | None, Never, Scope]:
|
|
219
|
+
try:
|
|
220
|
+
parsed = parse(element.element)
|
|
221
|
+
except PARSE_ERRORS as defect:
|
|
222
|
+
# a row that cannot be parsed never succeeds: dead-letter it,
|
|
223
|
+
# and the loop takes the next one
|
|
224
|
+
return self._settlements.fail(element, cause_die(defect)).as_(None)
|
|
225
|
+
return add_finalizer(lambda exit_: self._settle(options, element, exit_)).as_(
|
|
226
|
+
TakenElement(element.id, element.attempts, parsed)
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def next_or_retry(taken: TakenElement | None) -> Effect[TakenElement, Never, Scope]:
|
|
230
|
+
return suspend(loop) if taken is None else succeed(taken)
|
|
231
|
+
|
|
232
|
+
def loop() -> Effect[TakenElement, Never, Scope]:
|
|
233
|
+
return take_from(self._mailboxes, options.name, claimed).flat_map(next_or_retry)
|
|
234
|
+
|
|
235
|
+
def body() -> Effect[TakenElement, Never, Scope]:
|
|
236
|
+
self._states.get(options.name).max_attempts = options.max_attempts
|
|
237
|
+
return loop()
|
|
238
|
+
|
|
239
|
+
return suspend(body)
|
|
240
|
+
|
|
241
|
+
def _cleanup_state(self, state: str, time_to_live: Seconds) -> Effect[int, SqlError]:
|
|
242
|
+
return self._cleanup_batch(state, time_to_live).repeat(
|
|
243
|
+
schedule=spaced(_CLEANUP_BATCH_DELAY),
|
|
244
|
+
while_=lambda deleted: deleted == SQL_CLEANUP_BATCH_SIZE,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
def cleanup(
|
|
248
|
+
self, *, time_to_live: Seconds, failed_time_to_live: Seconds | None
|
|
249
|
+
) -> Effect[None, PersistedQueueError]:
|
|
250
|
+
def body() -> Generator[Any, Any]:
|
|
251
|
+
yield from self._cleanup_state("completed", time_to_live)
|
|
252
|
+
if failed_time_to_live is not None:
|
|
253
|
+
yield from self._cleanup_state("failed", failed_time_to_live)
|
|
254
|
+
|
|
255
|
+
return gen(body).catch_cause(_cleanup_failed)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def make_store_sql(
|
|
259
|
+
*,
|
|
260
|
+
table_name: str = "effect_queue",
|
|
261
|
+
poll_interval: Duration = _DEFAULT_POLL_INTERVAL,
|
|
262
|
+
lock_refresh_interval: Duration = _DEFAULT_LOCK_REFRESH_INTERVAL,
|
|
263
|
+
lock_expiration: Duration = _DEFAULT_LOCK_EXPIRATION,
|
|
264
|
+
) -> Effect[PersistedQueueStoreService, SqlError, SqlClient | Scope]:
|
|
265
|
+
"""A store in the table ``table_name`` (made and migrated if needed) of
|
|
266
|
+
the ``SqlClient``. Each poll claims rows under a lock of its own,
|
|
267
|
+
renewed every ``lock_refresh_interval`` while they are processed; a row
|
|
268
|
+
whose lock is older than ``lock_expiration`` (its worker died) is
|
|
269
|
+
claimed again, or marked failed if that was its last attempt. A
|
|
270
|
+
settlement or renewal only applies to the claim a handler received, so
|
|
271
|
+
a handler whose lock lapsed still settles its row until another poll
|
|
272
|
+
claims it. Delays use the database's clock, rounded up to whole
|
|
273
|
+
seconds."""
|
|
274
|
+
poll_every = store_interval(poll_interval, _DEFAULT_POLL_INTERVAL)
|
|
275
|
+
lock_refresh = store_interval(lock_refresh_interval, _DEFAULT_LOCK_REFRESH_INTERVAL)
|
|
276
|
+
lock_expiration_seconds = store_interval(lock_expiration, _DEFAULT_LOCK_EXPIRATION)
|
|
277
|
+
|
|
278
|
+
def build() -> Generator[Any, Any, PersistedQueueStoreService]:
|
|
279
|
+
sql = (yield from SqlClient()).without_transforms()
|
|
280
|
+
yield from migrator.make()(
|
|
281
|
+
loader=migrations(table_name), table=f"{table_name}_migrations"
|
|
282
|
+
).or_die()
|
|
283
|
+
dialect = Dialect(sql)
|
|
284
|
+
expires_at = dialect.seconds_ago(lock_expiration_seconds)
|
|
285
|
+
settlements = Settlements(dialect, table_name, ack_retry_schedule(lock_expiration_seconds))
|
|
286
|
+
yield from (
|
|
287
|
+
suspend(settlements.refresh_locks)
|
|
288
|
+
.tap_cause(log_warning)
|
|
289
|
+
.retry(spaced(0.5))
|
|
290
|
+
.schedule(fixed(lock_refresh))
|
|
291
|
+
.annotate_logs(
|
|
292
|
+
{
|
|
293
|
+
"package": "@effect/sql",
|
|
294
|
+
"module": "SqlPersistedQueue",
|
|
295
|
+
"fiber": "refreshLocks",
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
.fork_scoped()
|
|
299
|
+
)
|
|
300
|
+
states = QueueStates()
|
|
301
|
+
mailboxes = yield from RcMap.make(
|
|
302
|
+
_lookup(dialect, table_name, expires_at, poll_every, lock_refresh, states, settlements),
|
|
303
|
+
idle_time_to_live=30.0,
|
|
304
|
+
)
|
|
305
|
+
store = _SqlStore(
|
|
306
|
+
dialect,
|
|
307
|
+
offer_statement(dialect, table_name),
|
|
308
|
+
cleanup_statement(dialect, table_name),
|
|
309
|
+
settlements,
|
|
310
|
+
states,
|
|
311
|
+
mailboxes,
|
|
312
|
+
)
|
|
313
|
+
return PersistedQueueStoreService(offer=store.offer, take=store.take, cleanup=store.cleanup)
|
|
314
|
+
|
|
315
|
+
return gen(build)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def layer_store_sql(
|
|
319
|
+
*,
|
|
320
|
+
table_name: str = "effect_queue",
|
|
321
|
+
poll_interval: Duration = _DEFAULT_POLL_INTERVAL,
|
|
322
|
+
lock_refresh_interval: Duration = _DEFAULT_LOCK_REFRESH_INTERVAL,
|
|
323
|
+
lock_expiration: Duration = _DEFAULT_LOCK_EXPIRATION,
|
|
324
|
+
) -> Layer[PersistedQueueStore, SqlError, SqlClient]:
|
|
325
|
+
"""The store of ``make_store_sql``."""
|
|
326
|
+
return layer_effect(
|
|
327
|
+
PersistedQueueStore,
|
|
328
|
+
make_store_sql(
|
|
329
|
+
table_name=table_name,
|
|
330
|
+
poll_interval=poll_interval,
|
|
331
|
+
lock_refresh_interval=lock_refresh_interval,
|
|
332
|
+
lock_expiration=lock_expiration,
|
|
333
|
+
),
|
|
334
|
+
)
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""The migrations of the SQL queue table, as Effect's: ``0001`` makes the
|
|
2
|
+
first schema (a ``completed`` flag), ``0002`` upgrades it to ``state``
|
|
3
|
+
and ``visible_at`` with a partial index for pollers."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from collections.abc import Generator
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from pyfect._core import Effect
|
|
11
|
+
from pyfect.effect import gen
|
|
12
|
+
from pyfect.sql import migrator
|
|
13
|
+
from pyfect.sql.client import SqlClient, SqlClientService
|
|
14
|
+
from pyfect.sql.error import SqlError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _create_table(sql: SqlClientService, table_name: str) -> Effect[object, SqlError]:
|
|
18
|
+
table = sql.identifier(table_name)
|
|
19
|
+
return sql.on_dialect_or_else(
|
|
20
|
+
mysql=lambda: sql(
|
|
21
|
+
"CREATE TABLE IF NOT EXISTS {} ("
|
|
22
|
+
"sequence BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, "
|
|
23
|
+
"id VARCHAR(36) NOT NULL, "
|
|
24
|
+
"queue_name VARCHAR(100) NOT NULL, "
|
|
25
|
+
"element TEXT NOT NULL, "
|
|
26
|
+
"completed BOOLEAN NOT NULL, "
|
|
27
|
+
"attempts INT NOT NULL DEFAULT 0, "
|
|
28
|
+
"last_failure TEXT NULL, "
|
|
29
|
+
"acquired_at DATETIME NULL, "
|
|
30
|
+
"acquired_by VARCHAR(36) NULL, "
|
|
31
|
+
"created_at DATETIME NOT NULL, "
|
|
32
|
+
"updated_at DATETIME NOT NULL)",
|
|
33
|
+
table,
|
|
34
|
+
),
|
|
35
|
+
pg=lambda: sql(
|
|
36
|
+
"CREATE TABLE IF NOT EXISTS {} ("
|
|
37
|
+
"sequence SERIAL PRIMARY KEY, "
|
|
38
|
+
"id VARCHAR(36) NOT NULL, "
|
|
39
|
+
"queue_name VARCHAR(100) NOT NULL, "
|
|
40
|
+
"element TEXT NOT NULL, "
|
|
41
|
+
"completed BOOLEAN NOT NULL, "
|
|
42
|
+
"attempts INTEGER NOT NULL DEFAULT 0, "
|
|
43
|
+
"last_failure TEXT NULL, "
|
|
44
|
+
"acquired_at TIMESTAMP NULL, "
|
|
45
|
+
"acquired_by UUID NULL, "
|
|
46
|
+
"created_at TIMESTAMP NOT NULL, "
|
|
47
|
+
"updated_at TIMESTAMP NOT NULL)",
|
|
48
|
+
table,
|
|
49
|
+
),
|
|
50
|
+
mssql=lambda: sql(
|
|
51
|
+
"IF NOT EXISTS (SELECT * FROM sysobjects WHERE name={} AND xtype='U') "
|
|
52
|
+
"CREATE TABLE {} ("
|
|
53
|
+
"sequence INT IDENTITY(1,1) PRIMARY KEY, "
|
|
54
|
+
"id NVARCHAR(36) NOT NULL, "
|
|
55
|
+
"queue_name NVARCHAR(100) NOT NULL, "
|
|
56
|
+
"element NVARCHAR(MAX) NOT NULL, "
|
|
57
|
+
"completed BIT NOT NULL, "
|
|
58
|
+
"attempts INT NOT NULL DEFAULT 0, "
|
|
59
|
+
"last_failure NVARCHAR(MAX) NULL, "
|
|
60
|
+
"acquired_at DATETIME2 NULL, "
|
|
61
|
+
"acquired_by UNIQUEIDENTIFIER NULL, "
|
|
62
|
+
"created_at DATETIME2 NOT NULL, "
|
|
63
|
+
"updated_at DATETIME2 NOT NULL)",
|
|
64
|
+
table_name,
|
|
65
|
+
table,
|
|
66
|
+
),
|
|
67
|
+
or_else=lambda: sql(
|
|
68
|
+
"CREATE TABLE IF NOT EXISTS {} ("
|
|
69
|
+
"sequence INTEGER PRIMARY KEY AUTOINCREMENT, "
|
|
70
|
+
"id TEXT NOT NULL, "
|
|
71
|
+
"queue_name TEXT NOT NULL, "
|
|
72
|
+
"element TEXT NOT NULL, "
|
|
73
|
+
"completed BOOLEAN NOT NULL, "
|
|
74
|
+
"attempts INTEGER NOT NULL DEFAULT 0, "
|
|
75
|
+
"last_failure TEXT NULL, "
|
|
76
|
+
"acquired_at DATETIME NULL, "
|
|
77
|
+
"acquired_by TEXT NULL, "
|
|
78
|
+
"created_at DATETIME NOT NULL, "
|
|
79
|
+
"updated_at DATETIME NOT NULL)",
|
|
80
|
+
table,
|
|
81
|
+
),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _index(
|
|
86
|
+
sql: SqlClientService, table_name: str, suffix: str, columns: str, *, unique: bool = False
|
|
87
|
+
) -> Effect[object, SqlError]:
|
|
88
|
+
"""``CREATE [UNIQUE] INDEX idx_<table>_<suffix> ON <table> (<columns>)``
|
|
89
|
+
unless it exists (MySQL has no ``IF NOT EXISTS``: the error is
|
|
90
|
+
ignored)."""
|
|
91
|
+
table = sql.identifier(table_name)
|
|
92
|
+
index_name = f"idx_{table_name}_{suffix}"
|
|
93
|
+
index = sql.identifier(index_name)
|
|
94
|
+
kind = sql.literal("UNIQUE INDEX" if unique else "INDEX")
|
|
95
|
+
cols = sql.unsafe(columns)
|
|
96
|
+
return sql.on_dialect_or_else(
|
|
97
|
+
mssql=lambda: sql(
|
|
98
|
+
"IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = {}) CREATE {} {} ON {} ({})",
|
|
99
|
+
index_name,
|
|
100
|
+
kind,
|
|
101
|
+
index,
|
|
102
|
+
table,
|
|
103
|
+
cols,
|
|
104
|
+
),
|
|
105
|
+
mysql=lambda: sql("CREATE {} {} ON {} ({})", kind, index, table, cols).ignore(),
|
|
106
|
+
or_else=lambda: sql("CREATE {} IF NOT EXISTS {} ON {} ({})", kind, index, table, cols),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _create(table_name: str) -> Effect[object, object, SqlClient]:
|
|
111
|
+
def body() -> Generator[Any, Any]:
|
|
112
|
+
sql = (yield from SqlClient()).without_transforms()
|
|
113
|
+
yield from _create_table(sql, table_name)
|
|
114
|
+
yield from _index(sql, table_name, "id", "id, queue_name", unique=True)
|
|
115
|
+
yield from _index(sql, table_name, "take", "queue_name, completed, attempts, acquired_at")
|
|
116
|
+
yield from _index(sql, table_name, "update", "sequence, acquired_by")
|
|
117
|
+
|
|
118
|
+
return gen(body)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
_UPGRADED_COLUMNS = (
|
|
122
|
+
"sequence, id, queue_name, element, state, attempts, last_failure, visible_at, "
|
|
123
|
+
"acquired_at, acquired_by, created_at, updated_at"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _upgrade_pg(sql: SqlClientService, table_name: str) -> Effect[object, SqlError]:
|
|
128
|
+
table = sql.identifier(table_name)
|
|
129
|
+
take_index = sql.identifier(f"idx_{table_name}_take")
|
|
130
|
+
|
|
131
|
+
def body() -> Generator[Any, Any]:
|
|
132
|
+
yield from sql("DROP INDEX IF EXISTS {}", take_index)
|
|
133
|
+
yield from sql(
|
|
134
|
+
"ALTER TABLE {} ADD COLUMN state VARCHAR(10), ADD COLUMN visible_at TIMESTAMP", table
|
|
135
|
+
)
|
|
136
|
+
yield from sql(
|
|
137
|
+
"UPDATE {} SET state = CASE WHEN completed THEN 'completed' ELSE 'pending' END, "
|
|
138
|
+
"visible_at = updated_at",
|
|
139
|
+
table,
|
|
140
|
+
)
|
|
141
|
+
yield from sql(
|
|
142
|
+
"ALTER TABLE {} "
|
|
143
|
+
"ALTER COLUMN sequence TYPE BIGINT, "
|
|
144
|
+
"ALTER COLUMN id TYPE VARCHAR(255), "
|
|
145
|
+
"ALTER COLUMN queue_name TYPE VARCHAR(255), "
|
|
146
|
+
"ALTER COLUMN state SET NOT NULL, "
|
|
147
|
+
"ALTER COLUMN visible_at SET NOT NULL, "
|
|
148
|
+
"DROP COLUMN completed",
|
|
149
|
+
table,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return gen(body)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _upgrade_mysql(sql: SqlClientService, table_name: str) -> Effect[object, SqlError]:
|
|
156
|
+
table = sql.identifier(table_name)
|
|
157
|
+
take_index = sql.identifier(f"idx_{table_name}_take")
|
|
158
|
+
|
|
159
|
+
def body() -> Generator[Any, Any]:
|
|
160
|
+
yield from sql("DROP INDEX {} ON {}", take_index, table)
|
|
161
|
+
yield from sql(
|
|
162
|
+
"ALTER TABLE {} "
|
|
163
|
+
"MODIFY COLUMN id VARCHAR(255) NOT NULL, "
|
|
164
|
+
"MODIFY COLUMN queue_name VARCHAR(255) NOT NULL, "
|
|
165
|
+
"MODIFY COLUMN element MEDIUMTEXT NOT NULL, "
|
|
166
|
+
"MODIFY COLUMN last_failure MEDIUMTEXT NULL, "
|
|
167
|
+
"ADD COLUMN state VARCHAR(10) NULL, "
|
|
168
|
+
"ADD COLUMN visible_at DATETIME NULL",
|
|
169
|
+
table,
|
|
170
|
+
)
|
|
171
|
+
yield from sql(
|
|
172
|
+
"UPDATE {} SET state = CASE WHEN completed THEN 'completed' ELSE 'pending' END, "
|
|
173
|
+
"visible_at = updated_at",
|
|
174
|
+
table,
|
|
175
|
+
)
|
|
176
|
+
yield from sql(
|
|
177
|
+
"ALTER TABLE {} "
|
|
178
|
+
"MODIFY COLUMN state VARCHAR(10) NOT NULL, "
|
|
179
|
+
"MODIFY COLUMN visible_at DATETIME NOT NULL, "
|
|
180
|
+
"DROP COLUMN completed",
|
|
181
|
+
table,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
return gen(body)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _upgrade_mssql(sql: SqlClientService, table_name: str) -> Effect[object, SqlError]:
|
|
188
|
+
table = sql.identifier(table_name)
|
|
189
|
+
upgraded_name = f"{table_name}_upgrade"
|
|
190
|
+
upgraded = sql.identifier(upgraded_name)
|
|
191
|
+
|
|
192
|
+
def body() -> Generator[Any, Any]:
|
|
193
|
+
yield from sql(
|
|
194
|
+
"CREATE TABLE {} ("
|
|
195
|
+
"sequence BIGINT IDENTITY(1,1) PRIMARY KEY, "
|
|
196
|
+
"id NVARCHAR(255) NOT NULL, "
|
|
197
|
+
"queue_name NVARCHAR(255) NOT NULL, "
|
|
198
|
+
"element NVARCHAR(MAX) NOT NULL, "
|
|
199
|
+
"state NVARCHAR(10) NOT NULL, "
|
|
200
|
+
"attempts INT NOT NULL DEFAULT 0, "
|
|
201
|
+
"last_failure NVARCHAR(MAX) NULL, "
|
|
202
|
+
"visible_at DATETIME2 NOT NULL, "
|
|
203
|
+
"acquired_at DATETIME2 NULL, "
|
|
204
|
+
"acquired_by UNIQUEIDENTIFIER NULL, "
|
|
205
|
+
"created_at DATETIME2 NOT NULL, "
|
|
206
|
+
"updated_at DATETIME2 NOT NULL)",
|
|
207
|
+
upgraded,
|
|
208
|
+
)
|
|
209
|
+
yield from sql(
|
|
210
|
+
"SET IDENTITY_INSERT {} ON; "
|
|
211
|
+
"INSERT INTO {} (" + _UPGRADED_COLUMNS + ") "
|
|
212
|
+
"SELECT sequence, id, queue_name, element, "
|
|
213
|
+
"CASE WHEN completed = 1 THEN 'completed' ELSE 'pending' END, "
|
|
214
|
+
"attempts, last_failure, updated_at, acquired_at, acquired_by, created_at, updated_at "
|
|
215
|
+
"FROM {}; "
|
|
216
|
+
"SET IDENTITY_INSERT {} OFF",
|
|
217
|
+
upgraded,
|
|
218
|
+
upgraded,
|
|
219
|
+
table,
|
|
220
|
+
upgraded,
|
|
221
|
+
)
|
|
222
|
+
yield from sql("DROP TABLE {}", table)
|
|
223
|
+
yield from sql("EXEC sp_rename {}, {}", upgraded_name, table_name)
|
|
224
|
+
yield from sql(
|
|
225
|
+
"CREATE UNIQUE INDEX {} ON {} (id, queue_name)",
|
|
226
|
+
sql.identifier(f"idx_{table_name}_id"),
|
|
227
|
+
table,
|
|
228
|
+
)
|
|
229
|
+
yield from sql(
|
|
230
|
+
"CREATE INDEX {} ON {} (sequence, acquired_by)",
|
|
231
|
+
sql.identifier(f"idx_{table_name}_update"),
|
|
232
|
+
table,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
return gen(body)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _upgrade_sqlite(sql: SqlClientService, table_name: str) -> Effect[object, SqlError]:
|
|
239
|
+
"""Rebuilds the table: altering or dropping constrained columns is not
|
|
240
|
+
portable across SQLite versions."""
|
|
241
|
+
table = sql.identifier(table_name)
|
|
242
|
+
upgraded = sql.identifier(f"{table_name}_upgrade")
|
|
243
|
+
|
|
244
|
+
def body() -> Generator[Any, Any]:
|
|
245
|
+
yield from sql("DROP INDEX IF EXISTS {}", sql.identifier(f"idx_{table_name}_take"))
|
|
246
|
+
yield from sql(
|
|
247
|
+
"CREATE TABLE {} ("
|
|
248
|
+
"sequence INTEGER PRIMARY KEY AUTOINCREMENT, "
|
|
249
|
+
"id TEXT NOT NULL, "
|
|
250
|
+
"queue_name TEXT NOT NULL, "
|
|
251
|
+
"element TEXT NOT NULL, "
|
|
252
|
+
"state TEXT NOT NULL, "
|
|
253
|
+
"attempts INTEGER NOT NULL DEFAULT 0, "
|
|
254
|
+
"last_failure TEXT NULL, "
|
|
255
|
+
"visible_at DATETIME NOT NULL, "
|
|
256
|
+
"acquired_at DATETIME NULL, "
|
|
257
|
+
"acquired_by TEXT NULL, "
|
|
258
|
+
"created_at DATETIME NOT NULL, "
|
|
259
|
+
"updated_at DATETIME NOT NULL)",
|
|
260
|
+
upgraded,
|
|
261
|
+
)
|
|
262
|
+
yield from sql(
|
|
263
|
+
"INSERT INTO {} (" + _UPGRADED_COLUMNS + ") "
|
|
264
|
+
"SELECT sequence, id, queue_name, element, "
|
|
265
|
+
"CASE WHEN completed THEN 'completed' ELSE 'pending' END, "
|
|
266
|
+
"attempts, last_failure, updated_at, acquired_at, acquired_by, created_at, updated_at "
|
|
267
|
+
"FROM {}",
|
|
268
|
+
upgraded,
|
|
269
|
+
table,
|
|
270
|
+
)
|
|
271
|
+
yield from sql("DROP TABLE {}", table)
|
|
272
|
+
yield from sql("ALTER TABLE {} RENAME TO {}", upgraded, table)
|
|
273
|
+
yield from sql(
|
|
274
|
+
"CREATE UNIQUE INDEX {} ON {} (id, queue_name)",
|
|
275
|
+
sql.identifier(f"idx_{table_name}_id"),
|
|
276
|
+
table,
|
|
277
|
+
)
|
|
278
|
+
yield from sql(
|
|
279
|
+
"CREATE INDEX {} ON {} (sequence, acquired_by)",
|
|
280
|
+
sql.identifier(f"idx_{table_name}_update"),
|
|
281
|
+
table,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
return gen(body)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _upgrade(table_name: str) -> Effect[object, object, SqlClient]:
|
|
288
|
+
def body() -> Generator[Any, Any]:
|
|
289
|
+
sql = (yield from SqlClient()).without_transforms()
|
|
290
|
+
table = sql.identifier(table_name)
|
|
291
|
+
take_index = sql.identifier(f"idx_{table_name}_take")
|
|
292
|
+
yield from sql.on_dialect_or_else(
|
|
293
|
+
pg=lambda: _upgrade_pg(sql, table_name),
|
|
294
|
+
mysql=lambda: _upgrade_mysql(sql, table_name),
|
|
295
|
+
mssql=lambda: _upgrade_mssql(sql, table_name),
|
|
296
|
+
or_else=lambda: _upgrade_sqlite(sql, table_name),
|
|
297
|
+
)
|
|
298
|
+
# a partial index where supported, so pollers never scan completed rows
|
|
299
|
+
yield from sql.on_dialect_or_else(
|
|
300
|
+
mysql=lambda: sql(
|
|
301
|
+
"CREATE INDEX {} ON {} (queue_name, state, visible_at)", take_index, table
|
|
302
|
+
),
|
|
303
|
+
or_else=lambda: sql(
|
|
304
|
+
"CREATE INDEX {} ON {} (queue_name, visible_at) WHERE state = 'pending'",
|
|
305
|
+
take_index,
|
|
306
|
+
table,
|
|
307
|
+
),
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
return gen(body)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def migrations(table_name: str) -> migrator.Loader:
|
|
314
|
+
return migrator.from_record(
|
|
315
|
+
{
|
|
316
|
+
"0001_create_table": _create(table_name),
|
|
317
|
+
"0002_upgrade_schema": _upgrade(table_name),
|
|
318
|
+
}
|
|
319
|
+
)
|