pyfect-dev 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyfect/__init__.py +729 -0
- pyfect/_all.py +242 -0
- pyfect/_background.py +28 -0
- pyfect/_binary_codecs.py +124 -0
- pyfect/_cached.py +165 -0
- pyfect/_catch.py +128 -0
- pyfect/_catch_reason.py +574 -0
- pyfect/_concurrency.py +291 -0
- pyfect/_context_effects.py +163 -0
- pyfect/_context_managers.py +76 -0
- pyfect/_core.py +368 -0
- pyfect/_digits.py +71 -0
- pyfect/_effect_args.py +46 -0
- pyfect/_effect_base.py +71 -0
- pyfect/_effect_concurrency.py +133 -0
- pyfect/_effect_eager.py +103 -0
- pyfect/_effect_errors.py +344 -0
- pyfect/_effect_folding.py +110 -0
- pyfect/_effect_hash.py +140 -0
- pyfect/_effect_logging.py +57 -0
- pyfect/_effect_metrics.py +102 -0
- pyfect/_effect_resources.py +179 -0
- pyfect/_effect_services.py +25 -0
- pyfect/_effect_timing.py +199 -0
- pyfect/_effect_tracing.py +99 -0
- pyfect/_equal.py +195 -0
- pyfect/_extras.py +43 -0
- pyfect/_fiber.py +969 -0
- pyfect/_fiber_all.py +134 -0
- pyfect/_fiber_collections.py +106 -0
- pyfect/_fn.py +706 -0
- pyfect/_hamt.py +174 -0
- pyfect/_interrupt_reason.py +33 -0
- pyfect/_isaac.py +149 -0
- pyfect/_iterables.py +231 -0
- pyfect/_js.py +263 -0
- pyfect/_js_date.py +87 -0
- pyfect/_json_text.py +286 -0
- pyfect/_json_value.py +9 -0
- pyfect/_layer_core.py +403 -0
- pyfect/_layer_for_life.py +33 -0
- pyfect/_layer_tracing.py +70 -0
- pyfect/_locked_ref.py +147 -0
- pyfect/_loop_thread.py +152 -0
- pyfect/_metric_ref.py +29 -0
- pyfect/_one_or_many.py +14 -0
- pyfect/_os_wait.py +118 -0
- pyfect/_path_posix.py +129 -0
- pyfect/_primitives.py +434 -0
- pyfect/_public.py +36 -0
- pyfect/_reasons.py +64 -0
- pyfect/_released.py +67 -0
- pyfect/_root.py +87 -0
- pyfect/_round_robin.py +73 -0
- pyfect/_some.py +17 -0
- pyfect/_stack.py +52 -0
- pyfect/_stack_frame.py +114 -0
- pyfect/_string_hash.py +35 -0
- pyfect/_subscriber_queue.py +47 -0
- pyfect/_tags.py +14 -0
- pyfect/_tracer_ref.py +21 -0
- pyfect/_tx_ops.py +77 -0
- pyfect/_unsubscripted.py +23 -0
- pyfect/_url.py +54 -0
- pyfect/_waiters.py +127 -0
- pyfect/_wake_tracking.py +98 -0
- pyfect/_wire_codec.py +123 -0
- pyfect/ai/__init__.py +73 -0
- pyfect/ai/_approvals.py +249 -0
- pyfect/ai/_decision_answers.py +185 -0
- pyfect/ai/_generated_client.py +298 -0
- pyfect/ai/_generated_support.py +84 -0
- pyfect/ai/_mcp/__init__.py +1 -0
- pyfect/ai/_mcp/batch.py +68 -0
- pyfect/ai/_mcp/client_transport.py +412 -0
- pyfect/ai/_mcp/client_wire.py +284 -0
- pyfect/ai/_mcp/core.py +490 -0
- pyfect/ai/_mcp/dispatch.py +1048 -0
- pyfect/ai/_mcp/http.py +462 -0
- pyfect/ai/_mcp/http_admission.py +269 -0
- pyfect/ai/_mcp/http_status.py +27 -0
- pyfect/ai/_mcp/input_wire.py +226 -0
- pyfect/ai/_mcp/log_levels.py +55 -0
- pyfect/ai/_mcp/param_headers.py +63 -0
- pyfect/ai/_mcp/projection.py +248 -0
- pyfect/ai/_mcp/register.py +683 -0
- pyfect/ai/_mcp/reverse_policy.py +155 -0
- pyfect/ai/_mcp/routing_header.py +24 -0
- pyfect/ai/_mcp/sessions.py +114 -0
- pyfect/ai/_mcp/stdio.py +110 -0
- pyfect/ai/_mcp/subscriptions.py +134 -0
- pyfect/ai/_mcp/tool_schema.py +35 -0
- pyfect/ai/_noul_answers.py +64 -0
- pyfect/ai/_openai_all_of.py +137 -0
- pyfect/ai/_openai_errors.py +233 -0
- pyfect/ai/_provider.py +439 -0
- pyfect/ai/_provider_tools.py +129 -0
- pyfect/ai/_stream_resolution.py +231 -0
- pyfect/ai/_structured_output_checks.py +138 -0
- pyfect/ai/_structured_output_codec.py +229 -0
- pyfect/ai/_structured_output_json.py +109 -0
- pyfect/ai/_tool_call_ids.py +80 -0
- pyfect/ai/ai_error.py +785 -0
- pyfect/ai/anthropic/__init__.py +28 -0
- pyfect/ai/anthropic/_errors.py +164 -0
- pyfect/ai/anthropic/_prompt.py +375 -0
- pyfect/ai/anthropic/_response.py +443 -0
- pyfect/ai/anthropic/_schema.py +206 -0
- pyfect/ai/anthropic/_stream.py +377 -0
- pyfect/ai/anthropic/_tools.py +248 -0
- pyfect/ai/anthropic/client.py +289 -0
- pyfect/ai/anthropic/config.py +32 -0
- pyfect/ai/anthropic/error.py +27 -0
- pyfect/ai/anthropic/language_model.py +366 -0
- pyfect/ai/anthropic/telemetry.py +51 -0
- pyfect/ai/anthropic/tool.py +570 -0
- pyfect/ai/anthropic_structured_output.py +138 -0
- pyfect/ai/chat.py +548 -0
- pyfect/ai/cloudflare/__init__.py +15 -0
- pyfect/ai/cloudflare/_errors.py +108 -0
- pyfect/ai/cloudflare/client.py +174 -0
- pyfect/ai/cloudflare/decision_model.py +117 -0
- pyfect/ai/cloudflare/schema.py +144 -0
- pyfect/ai/decision.py +290 -0
- pyfect/ai/decision_model.py +283 -0
- pyfect/ai/embedding_model.py +177 -0
- pyfect/ai/id_generator.py +79 -0
- pyfect/ai/language_model.py +1077 -0
- pyfect/ai/mcp_client.py +682 -0
- pyfect/ai/mcp_client_error.py +223 -0
- pyfect/ai/mcp_protocol.py +205 -0
- pyfect/ai/mcp_schema.py +1300 -0
- pyfect/ai/mcp_server.py +404 -0
- pyfect/ai/model.py +56 -0
- pyfect/ai/openai/__init__.py +49 -0
- pyfect/ai/openai/_errors.py +89 -0
- pyfect/ai/openai/_prompt.py +527 -0
- pyfect/ai/openai/_requests.py +34 -0
- pyfect/ai/openai/_response.py +569 -0
- pyfect/ai/openai/_schema.py +769 -0
- pyfect/ai/openai/_stream.py +771 -0
- pyfect/ai/openai/_tools.py +128 -0
- pyfect/ai/openai/_web_socket.py +295 -0
- pyfect/ai/openai/client.py +296 -0
- pyfect/ai/openai/client_generated.py +123 -0
- pyfect/ai/openai/config.py +34 -0
- pyfect/ai/openai/decision_model.py +177 -0
- pyfect/ai/openai/embedding_model.py +116 -0
- pyfect/ai/openai/error.py +8 -0
- pyfect/ai/openai/generated/__init__.py +7213 -0
- pyfect/ai/openai/generated/_recursive.py +18 -0
- pyfect/ai/openai/generated/_schemas_0.py +1015 -0
- pyfect/ai/openai/generated/_schemas_1.py +1016 -0
- pyfect/ai/openai/generated/_schemas_2.py +1017 -0
- pyfect/ai/openai/generated/_schemas_3.py +615 -0
- pyfect/ai/openai/generated/client.py +5298 -0
- pyfect/ai/openai/generated/types.py +8123 -0
- pyfect/ai/openai/language_model.py +304 -0
- pyfect/ai/openai/telemetry.py +19 -0
- pyfect/ai/openai/tool.py +391 -0
- pyfect/ai/openai_compat/__init__.py +25 -0
- pyfect/ai/openai_compat/_prompt.py +191 -0
- pyfect/ai/openai_compat/_request.py +153 -0
- pyfect/ai/openai_compat/_response.py +407 -0
- pyfect/ai/openai_compat/_schema.py +97 -0
- pyfect/ai/openai_compat/client.py +234 -0
- pyfect/ai/openai_compat/config.py +32 -0
- pyfect/ai/openai_compat/embedding_model.py +139 -0
- pyfect/ai/openai_compat/error.py +7 -0
- pyfect/ai/openai_compat/language_model.py +245 -0
- pyfect/ai/openai_compat/telemetry.py +51 -0
- pyfect/ai/openai_structured_output.py +118 -0
- pyfect/ai/openrouter/__init__.py +36 -0
- pyfect/ai/openrouter/_errors.py +186 -0
- pyfect/ai/openrouter/_prompt.py +272 -0
- pyfect/ai/openrouter/_response.py +226 -0
- pyfect/ai/openrouter/_stream.py +320 -0
- pyfect/ai/openrouter/client.py +283 -0
- pyfect/ai/openrouter/config.py +36 -0
- pyfect/ai/openrouter/decision_model.py +161 -0
- pyfect/ai/openrouter/error.py +23 -0
- pyfect/ai/openrouter/generated/__init__.py +3029 -0
- pyfect/ai/openrouter/generated/_recursive.py +15 -0
- pyfect/ai/openrouter/generated/_schemas_0.py +1015 -0
- pyfect/ai/openrouter/generated/_schemas_1.py +522 -0
- pyfect/ai/openrouter/generated/client.py +1445 -0
- pyfect/ai/openrouter/generated/types.py +4769 -0
- pyfect/ai/openrouter/language_model.py +345 -0
- pyfect/ai/openrouter/schema.py +165 -0
- pyfect/ai/prompt.py +664 -0
- pyfect/ai/response.py +768 -0
- pyfect/ai/response_id_tracker.py +82 -0
- pyfect/ai/telemetry.py +126 -0
- pyfect/ai/tokenizer.py +61 -0
- pyfect/ai/tool.py +536 -0
- pyfect/ai/toolkit.py +490 -0
- pyfect/ai/typesafe/__init__.py +17 -0
- pyfect/ai/typesafe/_errors.py +120 -0
- pyfect/ai/typesafe/client.py +146 -0
- pyfect/ai/typesafe/config.py +34 -0
- pyfect/ai/typesafe/decision_model.py +90 -0
- pyfect/ai/typesafe/schema.py +140 -0
- pyfect/batching/__init__.py +58 -0
- pyfect/batching/_api.py +71 -0
- pyfect/batching/_engine.py +229 -0
- pyfect/batching/_from_effect.py +76 -0
- pyfect/batching/_persisted.py +98 -0
- pyfect/batching/_request.py +133 -0
- pyfect/batching/_resolver.py +477 -0
- pyfect/batching/_with_cache.py +90 -0
- pyfect/big_decimal.py +500 -0
- pyfect/brand.py +114 -0
- pyfect/byte_size.py +354 -0
- pyfect/cache/__init__.py +29 -0
- pyfect/cache/_cache.py +426 -0
- pyfect/cache/_expiry.py +69 -0
- pyfect/cache/_scoped_cache.py +546 -0
- pyfect/case.py +174 -0
- pyfect/cause.py +462 -0
- pyfect/channel/__init__.py +155 -0
- pyfect/channel/_base.py +92 -0
- pyfect/channel/_catch.py +158 -0
- pyfect/channel/_catch_reason.py +572 -0
- pyfect/channel/_channel.py +158 -0
- pyfect/channel/_combinators.py +220 -0
- pyfect/channel/_concurrency_methods.py +217 -0
- pyfect/channel/_concurrent_map.py +118 -0
- pyfect/channel/_constructors.py +349 -0
- pyfect/channel/_context.py +232 -0
- pyfect/channel/_error_methods.py +336 -0
- pyfect/channel/_errors.py +180 -0
- pyfect/channel/_filters.py +225 -0
- pyfect/channel/_lifecycle.py +71 -0
- pyfect/channel/_lifecycle_methods.py +206 -0
- pyfect/channel/_loop.py +100 -0
- pyfect/channel/_merge.py +246 -0
- pyfect/channel/_repeat.py +154 -0
- pyfect/channel/_run.py +149 -0
- pyfect/channel/_run_methods.py +341 -0
- pyfect/channel/_sinks.py +88 -0
- pyfect/channel/_sources.py +238 -0
- pyfect/channel/_text.py +176 -0
- pyfect/channel/_transform_methods.py +415 -0
- pyfect/channel/_upstream.py +67 -0
- pyfect/channel_schema.py +131 -0
- pyfect/cli/__init__.py +79 -0
- pyfect/cli/_ansi.py +82 -0
- pyfect/cli/_command.py +469 -0
- pyfect/cli/_config.py +164 -0
- pyfect/cli/_environment.py +10 -0
- pyfect/cli/_help.py +195 -0
- pyfect/cli/_lexer.py +81 -0
- pyfect/cli/_parser.py +465 -0
- pyfect/cli/_settings.py +85 -0
- pyfect/cli/_suggest.py +33 -0
- pyfect/cli/_width.py +134 -0
- pyfect/cli/_wizard.py +317 -0
- pyfect/cli/argument.py +212 -0
- pyfect/cli/cli_config.py +40 -0
- pyfect/cli/cli_error.py +242 -0
- pyfect/cli/cli_output.py +305 -0
- pyfect/cli/command.py +440 -0
- pyfect/cli/completions/__init__.py +50 -0
- pyfect/cli/completions/_completer.ps1 +190 -0
- pyfect/cli/completions/_descriptor.py +182 -0
- pyfect/cli/completions/bash.py +300 -0
- pyfect/cli/completions/fish.py +164 -0
- pyfect/cli/completions/powershell.py +156 -0
- pyfect/cli/completions/zsh.py +189 -0
- pyfect/cli/flag.py +246 -0
- pyfect/cli/global_flag.py +99 -0
- pyfect/cli/help_doc.py +63 -0
- pyfect/cli/param.py +789 -0
- pyfect/cli/primitive.py +308 -0
- pyfect/cli/prompt/__init__.py +75 -0
- pyfect/cli/prompt/_auto_complete.py +205 -0
- pyfect/cli/prompt/_choices.py +104 -0
- pyfect/cli/prompt/_confirm.py +195 -0
- pyfect/cli/prompt/_core.py +512 -0
- pyfect/cli/prompt/_date.py +173 -0
- pyfect/cli/prompt/_date_parts.py +387 -0
- pyfect/cli/prompt/_file.py +372 -0
- pyfect/cli/prompt/_multi_select.py +204 -0
- pyfect/cli/prompt/_number.py +257 -0
- pyfect/cli/prompt/_select.py +117 -0
- pyfect/cli/prompt/_text.py +239 -0
- pyfect/cli/terminal_capabilities.py +118 -0
- pyfect/clock.py +150 -0
- pyfect/cluster/__init__.py +155 -0
- pyfect/cluster/_cluster_abandon.py +36 -0
- pyfect/cluster/_cluster_layer.py +122 -0
- pyfect/cluster/_entity_activation.py +16 -0
- pyfect/cluster/_entity_manager/__init__.py +22 -0
- pyfect/cluster/_entity_manager/_activation.py +540 -0
- pyfect/cluster/_entity_manager/_core.py +186 -0
- pyfect/cluster/_entity_manager/_decode.py +60 -0
- pyfect/cluster/_entity_manager/_manager.py +423 -0
- pyfect/cluster/_entity_manager/_state.py +113 -0
- pyfect/cluster/_entity_reaper.py +94 -0
- pyfect/cluster/_hash.py +13 -0
- pyfect/cluster/_interruptors.py +88 -0
- pyfect/cluster/_resource_map.py +179 -0
- pyfect/cluster/_resource_ref.py +266 -0
- pyfect/cluster/_shard_lock.py +14 -0
- pyfect/cluster/cluster_cron.py +230 -0
- pyfect/cluster/cluster_error.py +81 -0
- pyfect/cluster/cluster_http.py +210 -0
- pyfect/cluster/cluster_metrics.py +54 -0
- pyfect/cluster/cluster_schema.py +137 -0
- pyfect/cluster/cluster_socket.py +197 -0
- pyfect/cluster/cluster_workflow_engine/__init__.py +24 -0
- pyfect/cluster/cluster_workflow_engine/_engine.py +1099 -0
- pyfect/cluster/cluster_workflow_engine/_entities.py +298 -0
- pyfect/cluster/deliver_at.py +31 -0
- pyfect/cluster/entity/__init__.py +39 -0
- pyfect/cluster/entity/_entity.py +332 -0
- pyfect/cluster/entity/_handlers.py +168 -0
- pyfect/cluster/entity/_keep_alive.py +74 -0
- pyfect/cluster/entity/_queue.py +103 -0
- pyfect/cluster/entity/_request.py +34 -0
- pyfect/cluster/entity/_services.py +23 -0
- pyfect/cluster/entity/_test_client.py +237 -0
- pyfect/cluster/entity_address.py +25 -0
- pyfect/cluster/entity_id.py +20 -0
- pyfect/cluster/entity_proxy.py +161 -0
- pyfect/cluster/entity_proxy_server.py +103 -0
- pyfect/cluster/entity_resource.py +122 -0
- pyfect/cluster/entity_type.py +20 -0
- pyfect/cluster/envelope.py +307 -0
- pyfect/cluster/http_runner.py +254 -0
- pyfect/cluster/k8s_http_client.py +281 -0
- pyfect/cluster/k8s_types.py +1394 -0
- pyfect/cluster/machine_id.py +21 -0
- pyfect/cluster/message.py +219 -0
- pyfect/cluster/message_storage/__init__.py +67 -0
- pyfect/cluster/message_storage/_encoded.py +393 -0
- pyfect/cluster/message_storage/_memory.py +386 -0
- pyfect/cluster/message_storage/_noop.py +109 -0
- pyfect/cluster/message_storage/_service.py +388 -0
- pyfect/cluster/reply.py +236 -0
- pyfect/cluster/runner.py +32 -0
- pyfect/cluster/runner_address.py +23 -0
- pyfect/cluster/runner_health.py +93 -0
- pyfect/cluster/runner_server.py +286 -0
- pyfect/cluster/runner_storage.py +212 -0
- pyfect/cluster/runners/__init__.py +55 -0
- pyfect/cluster/runners/_local.py +460 -0
- pyfect/cluster/runners/_noop.py +57 -0
- pyfect/cluster/runners/_rpc.py +290 -0
- pyfect/cluster/runners/_service.py +113 -0
- pyfect/cluster/shard_id.py +39 -0
- pyfect/cluster/sharding/__init__.py +12 -0
- pyfect/cluster/sharding/_acquisition.py +490 -0
- pyfect/cluster/sharding/_clients.py +271 -0
- pyfect/cluster/sharding/_make.py +388 -0
- pyfect/cluster/sharding/_routing.py +296 -0
- pyfect/cluster/sharding/_runners_sync.py +198 -0
- pyfect/cluster/sharding/_service.py +91 -0
- pyfect/cluster/sharding/_singletons.py +183 -0
- pyfect/cluster/sharding/_state.py +296 -0
- pyfect/cluster/sharding/_storage_loop.py +421 -0
- pyfect/cluster/sharding_config.py +230 -0
- pyfect/cluster/sharding_registration_event.py +28 -0
- pyfect/cluster/single_runner.py +49 -0
- pyfect/cluster/singleton.py +36 -0
- pyfect/cluster/singleton_address.py +20 -0
- pyfect/cluster/snowflake.py +133 -0
- pyfect/cluster/socket_runner.py +67 -0
- pyfect/cluster/sql_message_storage.py +1001 -0
- pyfect/cluster/sql_runner_storage.py +841 -0
- pyfect/cluster/test_runner.py +23 -0
- pyfect/config/__init__.py +93 -0
- pyfect/config/_all.py +96 -0
- pyfect/config/_collections.py +291 -0
- pyfect/config/_config.py +261 -0
- pyfect/config/_constructors.py +106 -0
- pyfect/config/_error.py +27 -0
- pyfect/config/_leaf.py +92 -0
- pyfect/config/_scalars.py +193 -0
- pyfect/config/_schema.py +340 -0
- pyfect/config_provider/__init__.py +60 -0
- pyfect/config_provider/_dir.py +84 -0
- pyfect/config_provider/_dotenv.py +127 -0
- pyfect/config_provider/_env.py +79 -0
- pyfect/config_provider/_layers.py +80 -0
- pyfect/config_provider/_node.py +90 -0
- pyfect/config_provider/_provider.py +118 -0
- pyfect/config_provider/_unknown.py +62 -0
- pyfect/console/__init__.py +63 -0
- pyfect/console/_effects.py +134 -0
- pyfect/console/_live.py +156 -0
- pyfect/console/_service.py +49 -0
- pyfect/console/_table.py +90 -0
- pyfect/context.py +414 -0
- pyfect/cron/__init__.py +30 -0
- pyfect/cron/_model.py +183 -0
- pyfect/cron/_search.py +268 -0
- pyfect/cron/_text.py +202 -0
- pyfect/cron/_wall.py +42 -0
- pyfect/crypto.py +257 -0
- pyfect/date_time/__init__.py +77 -0
- pyfect/date_time/_calendar.py +209 -0
- pyfect/date_time/_current.py +118 -0
- pyfect/date_time/_epoch.py +22 -0
- pyfect/date_time/_iso.py +80 -0
- pyfect/date_time/_zones.py +135 -0
- pyfect/date_time/intl.py +144 -0
- pyfect/deferred.py +145 -0
- pyfect/devtools/__init__.py +24 -0
- pyfect/devtools/client.py +323 -0
- pyfect/devtools/layers.py +32 -0
- pyfect/devtools/schema.py +301 -0
- pyfect/devtools/server.py +72 -0
- pyfect/duration.py +295 -0
- pyfect/effect.py +565 -0
- pyfect/effectable.py +37 -0
- pyfect/encoding/__init__.py +62 -0
- pyfect/encoding/_archive.py +197 -0
- pyfect/encoding/_json_array_scanner.py +477 -0
- pyfect/encoding/base64.py +23 -0
- pyfect/encoding/base64_url.py +19 -0
- pyfect/encoding/encoding_error.py +17 -0
- pyfect/encoding/hex.py +28 -0
- pyfect/encoding/ini.py +188 -0
- pyfect/encoding/json_array.py +246 -0
- pyfect/encoding/ndjson.py +461 -0
- pyfect/encoding/schema_binary/__init__.py +257 -0
- pyfect/encoding/schema_binary/_channels.py +173 -0
- pyfect/encoding/schema_binary/_compile.py +584 -0
- pyfect/encoding/schema_binary/_decode.py +848 -0
- pyfect/encoding/schema_binary/_dictionary.py +90 -0
- pyfect/encoding/schema_binary/_encode.py +838 -0
- pyfect/encoding/schema_binary/_fingerprint.py +168 -0
- pyfect/encoding/schema_binary/_layout.py +350 -0
- pyfect/encoding/schema_binary/_literals.py +47 -0
- pyfect/encoding/schema_binary/_parser.py +163 -0
- pyfect/encoding/schema_binary/_reader.py +148 -0
- pyfect/encoding/schema_binary/_target.py +310 -0
- pyfect/encoding/schema_binary/_values.py +133 -0
- pyfect/encoding/schema_binary/_wire.py +253 -0
- pyfect/encoding/sse.py +476 -0
- pyfect/encoding/tar/__init__.py +396 -0
- pyfect/encoding/tar/_decoder.py +220 -0
- pyfect/encoding/tar/_encoder.py +83 -0
- pyfect/encoding/tar/_header.py +494 -0
- pyfect/encoding/tar/_model.py +82 -0
- pyfect/encoding/toml.py +375 -0
- pyfect/encoding/xml/__init__.py +222 -0
- pyfect/encoding/xml/_chars.py +119 -0
- pyfect/encoding/xml/_collector.py +106 -0
- pyfect/encoding/xml/_inputs.py +126 -0
- pyfect/encoding/xml/_model.py +114 -0
- pyfect/encoding/xml/_parser.py +694 -0
- pyfect/encoding/xml/_scanner.py +180 -0
- pyfect/encoding/xml/_writer.py +238 -0
- pyfect/encoding/yaml.py +739 -0
- pyfect/encoding/zip/__init__.py +392 -0
- pyfect/encoding/zip/_decoder.py +378 -0
- pyfect/encoding/zip/_format.py +122 -0
- pyfect/encoding/zip/_inflate.py +65 -0
- pyfect/encoding/zip/_model.py +86 -0
- pyfect/encoding/zip/_writer.py +311 -0
- pyfect/error_reporter.py +240 -0
- pyfect/errors.py +321 -0
- pyfect/eventlog/__init__.py +77 -0
- pyfect/eventlog/_conflicts.py +94 -0
- pyfect/eventlog/_crypto.py +64 -0
- pyfect/eventlog/_identity.py +26 -0
- pyfect/eventlog/_identity_derivation.py +79 -0
- pyfect/eventlog/_transferable_bytes.py +55 -0
- pyfect/eventlog/_uuid.py +33 -0
- pyfect/eventlog/event.py +115 -0
- pyfect/eventlog/event_group.py +55 -0
- pyfect/eventlog/event_journal.py +630 -0
- pyfect/eventlog/event_log.py +1039 -0
- pyfect/eventlog/event_log_encryption.py +219 -0
- pyfect/eventlog/event_log_message.py +357 -0
- pyfect/eventlog/event_log_remote.py +465 -0
- pyfect/eventlog/event_log_server.py +442 -0
- pyfect/eventlog/event_log_server_encrypted.py +322 -0
- pyfect/eventlog/event_log_server_unencrypted.py +814 -0
- pyfect/eventlog/event_log_session_auth.py +250 -0
- pyfect/eventlog/event_row.py +423 -0
- pyfect/eventlog/sql_event_journal.py +692 -0
- pyfect/eventlog/sql_event_log_server_encrypted.py +440 -0
- pyfect/eventlog/sql_event_log_server_unencrypted.py +568 -0
- pyfect/execution_plan/__init__.py +42 -0
- pyfect/execution_plan/_events.py +128 -0
- pyfect/execution_plan/_model.py +108 -0
- pyfect/execution_plan/_run.py +176 -0
- pyfect/exit.py +205 -0
- pyfect/fiber_handle.py +305 -0
- pyfect/fiber_map.py +350 -0
- pyfect/fiber_set.py +232 -0
- pyfect/file_system/__init__.py +69 -0
- pyfect/file_system/_copy.py +116 -0
- pyfect/file_system/_errors.py +32 -0
- pyfect/file_system/_file.py +177 -0
- pyfect/file_system/_live.py +351 -0
- pyfect/file_system/_model.py +72 -0
- pyfect/file_system/_noop.py +189 -0
- pyfect/file_system/_service.py +336 -0
- pyfect/file_system/_stat.py +69 -0
- pyfect/file_system/_watch.py +107 -0
- pyfect/filters.py +245 -0
- pyfect/graph/__init__.py +101 -0
- pyfect/graph/_all_pairs.py +150 -0
- pyfect/graph/_combine.py +259 -0
- pyfect/graph/_compact.py +143 -0
- pyfect/graph/_components.py +200 -0
- pyfect/graph/_cycles.py +144 -0
- pyfect/graph/_enumerate.py +168 -0
- pyfect/graph/_errors.py +51 -0
- pyfect/graph/_flow.py +164 -0
- pyfect/graph/_forest.py +109 -0
- pyfect/graph/_graph.py +269 -0
- pyfect/graph/_lowlink.py +98 -0
- pyfect/graph/_matching.py +101 -0
- pyfect/graph/_mutate.py +211 -0
- pyfect/graph/_query.py +138 -0
- pyfect/graph/_records.py +119 -0
- pyfect/graph/_render.py +109 -0
- pyfect/graph/_shortest.py +274 -0
- pyfect/graph/_snapshot.py +50 -0
- pyfect/graph/_store.py +136 -0
- pyfect/graph/_traverse.py +289 -0
- pyfect/graph/_view.py +384 -0
- pyfect/graph/_walker.py +70 -0
- pyfect/graphql/__init__.py +87 -0
- pyfect/graphql/_codec.py +117 -0
- pyfect/graphql/_http.py +309 -0
- pyfect/graphql/_schemas.py +39 -0
- pyfect/graphql/_slices.py +110 -0
- pyfect/graphql/_template.py +111 -0
- pyfect/graphql/_types.py +115 -0
- pyfect/graphql/_ws.py +395 -0
- pyfect/graphql/_ws_messages.py +120 -0
- pyfect/graphql/client.py +428 -0
- pyfect/graphql/client_error.py +285 -0
- pyfect/graphql/group.py +142 -0
- pyfect/graphql/middleware.py +98 -0
- pyfect/graphql/operation.py +409 -0
- pyfect/graphql/protocol.py +119 -0
- pyfect/graphql/resolver.py +574 -0
- pyfect/graphql/template.py +61 -0
- pyfect/hash_map.py +317 -0
- pyfect/hash_ring.py +206 -0
- pyfect/hash_set.py +163 -0
- pyfect/http/__init__.py +125 -0
- pyfect/http/_asgi_listener.py +272 -0
- pyfect/http/_authority.py +63 -0
- pyfect/http/_brotli.py +27 -0
- pyfect/http/_compression.py +52 -0
- pyfect/http/_field.py +18 -0
- pyfect/http/_foreign_path.py +27 -0
- pyfect/http/_form_data.py +34 -0
- pyfect/http/_httpx_options.py +120 -0
- pyfect/http/_in_memory.py +76 -0
- pyfect/http/_request_state.py +59 -0
- pyfect/http/_vary.py +14 -0
- pyfect/http/abort.py +65 -0
- pyfect/http/asgi/__init__.py +59 -0
- pyfect/http/asgi/_app.py +155 -0
- pyfect/http/asgi/_http.py +201 -0
- pyfect/http/asgi/_mount.py +263 -0
- pyfect/http/asgi/_scope.py +86 -0
- pyfect/http/asgi/_serve.py +72 -0
- pyfect/http/asgi/_types.py +13 -0
- pyfect/http/asgi/_web_socket.py +325 -0
- pyfect/http/body.py +437 -0
- pyfect/http/client/__init__.py +111 -0
- pyfect/http/client/_circuit_breaker.py +113 -0
- pyfect/http/client/_cookie_jar.py +120 -0
- pyfect/http/client/_make.py +252 -0
- pyfect/http/client/_rate_limit.py +423 -0
- pyfect/http/client/_redirects.py +93 -0
- pyfect/http/client/_retry.py +115 -0
- pyfect/http/client/_service.py +497 -0
- pyfect/http/cookies.py +444 -0
- pyfect/http/error.py +188 -0
- pyfect/http/etag.py +96 -0
- pyfect/http/file_like.py +49 -0
- pyfect/http/find_my_way/__init__.py +10 -0
- pyfect/http/find_my_way/_nodes.py +186 -0
- pyfect/http/find_my_way/_paths.py +43 -0
- pyfect/http/find_my_way/_query_string.py +67 -0
- pyfect/http/find_my_way/_router.py +401 -0
- pyfect/http/headers.py +343 -0
- pyfect/http/http_effect.py +198 -0
- pyfect/http/httpx_client.py +291 -0
- pyfect/http/hypercorn_server.py +122 -0
- pyfect/http/incoming_message.py +140 -0
- pyfect/http/live_server/__init__.py +191 -0
- pyfect/http/live_server/_body.py +164 -0
- pyfect/http/live_server/_exchange.py +321 -0
- pyfect/http/live_server/_head.py +194 -0
- pyfect/http/live_server/_inbound.py +156 -0
- pyfect/http/live_server/_listener.py +211 -0
- pyfect/http/live_server/_serve.py +225 -0
- pyfect/http/live_server/_timers.py +20 -0
- pyfect/http/live_server/_upgrade.py +108 -0
- pyfect/http/live_server/_websocket.py +286 -0
- pyfect/http/live_server/_ws_frames.py +242 -0
- pyfect/http/method.py +30 -0
- pyfect/http/middleware/__init__.py +61 -0
- pyfect/http/middleware/_compression.py +156 -0
- pyfect/http/middleware/_cors.py +129 -0
- pyfect/http/middleware/_decompression.py +274 -0
- pyfect/http/middleware/_forwarded.py +113 -0
- pyfect/http/middleware/_logger.py +73 -0
- pyfect/http/middleware/_search_params.py +34 -0
- pyfect/http/middleware/_tracer.py +153 -0
- pyfect/http/middleware/_types.py +16 -0
- pyfect/http/mime/__init__.py +69 -0
- pyfect/http/mime/_types.py +361 -0
- pyfect/http/multipart.py +613 -0
- pyfect/http/multipart_parser/__init__.py +348 -0
- pyfect/http/multipart_parser/_content_type.py +109 -0
- pyfect/http/multipart_parser/_headers.py +206 -0
- pyfect/http/multipart_parser/_search.py +85 -0
- pyfect/http/origin.py +146 -0
- pyfect/http/platform.py +593 -0
- pyfect/http/prometheus.py +34 -0
- pyfect/http/request.py +384 -0
- pyfect/http/response.py +474 -0
- pyfect/http/router/__init__.py +82 -0
- pyfect/http/router/_api.py +462 -0
- pyfect/http/router/_in_memory.py +76 -0
- pyfect/http/router/_markers.py +47 -0
- pyfect/http/router/_middleware.py +181 -0
- pyfect/http/router/_middleware_stack.py +51 -0
- pyfect/http/router/_provided.py +31 -0
- pyfect/http/router/_route.py +114 -0
- pyfect/http/router/_router.py +308 -0
- pyfect/http/schema.py +276 -0
- pyfect/http/server.py +199 -0
- pyfect/http/server_error.py +261 -0
- pyfect/http/server_request.py +729 -0
- pyfect/http/server_respondable.py +77 -0
- pyfect/http/server_response.py +569 -0
- pyfect/http/static_server/__init__.py +257 -0
- pyfect/http/static_server/_conditional.py +72 -0
- pyfect/http/static_server/_mime.py +55 -0
- pyfect/http/static_server/_range.py +53 -0
- pyfect/http/status.py +51 -0
- pyfect/http/template.py +161 -0
- pyfect/http/trace_context.py +91 -0
- pyfect/http/url.py +442 -0
- pyfect/http/url_params.py +304 -0
- pyfect/http/uvicorn_server.py +119 -0
- pyfect/http/web.py +171 -0
- pyfect/http/web_handler.py +463 -0
- pyfect/http/wire.py +209 -0
- pyfect/http_api/__init__.py +83 -0
- pyfect/http_api/_assets/scalar.js +8 -0
- pyfect/http_api/_assets/swagger-ui.css +3 -0
- pyfect/http_api/_assets/swagger-ui.js +4 -0
- pyfect/http_api/_client_method.py +220 -0
- pyfect/http_api/_client_payload.py +144 -0
- pyfect/http_api/_client_response.py +377 -0
- pyfect/http_api/_handlers.py +282 -0
- pyfect/http_api/_html.py +38 -0
- pyfect/http_api/_media_type.py +6 -0
- pyfect/http_api/_openapi_responses.py +164 -0
- pyfect/http_api/_openapi_schemas.py +163 -0
- pyfect/http_api/_path.py +102 -0
- pyfect/http_api/_payload.py +260 -0
- pyfect/http_api/_response.py +363 -0
- pyfect/http_api/_security.py +174 -0
- pyfect/http_api/api.py +265 -0
- pyfect/http_api/builder.py +447 -0
- pyfect/http_api/client.py +474 -0
- pyfect/http_api/endpoint.py +969 -0
- pyfect/http_api/error.py +280 -0
- pyfect/http_api/group.py +215 -0
- pyfect/http_api/middleware.py +232 -0
- pyfect/http_api/openapi.py +396 -0
- pyfect/http_api/scalar.py +143 -0
- pyfect/http_api/schema.py +713 -0
- pyfect/http_api/security.py +91 -0
- pyfect/http_api/swagger.py +69 -0
- pyfect/http_api/test.py +105 -0
- pyfect/json_patch.py +255 -0
- pyfect/json_pointer.py +100 -0
- pyfect/json_schema/__init__.py +66 -0
- pyfect/json_schema/_api.py +96 -0
- pyfect/json_schema/_converter.py +258 -0
- pyfect/json_schema/_draft04.py +198 -0
- pyfect/json_schema/_draft07.py +203 -0
- pyfect/json_schema/_keywords.py +202 -0
- pyfect/json_schema/_model.py +201 -0
- pyfect/json_schema/_openapi.py +208 -0
- pyfect/latch.py +127 -0
- pyfect/layer.py +479 -0
- pyfect/layer_map.py +279 -0
- pyfect/layer_ref.py +208 -0
- pyfect/logger/__init__.py +91 -0
- pyfect/logger/_batched.py +49 -0
- pyfect/logger/_console.py +116 -0
- pyfect/logger/_file.py +31 -0
- pyfect/logger/_formats.py +104 -0
- pyfect/logger/_layer.py +50 -0
- pyfect/logger/_level.py +53 -0
- pyfect/logger/_log.py +142 -0
- pyfect/logger/_model.py +163 -0
- pyfect/logger/_refs.py +78 -0
- pyfect/logger/_stdlib.py +54 -0
- pyfect/logger/_text.py +263 -0
- pyfect/logger/_tracer.py +35 -0
- pyfect/managed_runtime.py +393 -0
- pyfect/metric/__init__.py +119 -0
- pyfect/metric/_boundaries.py +27 -0
- pyfect/metric/_kinds.py +417 -0
- pyfect/metric/_metric.py +302 -0
- pyfect/metric/_prometheus.py +150 -0
- pyfect/metric/_refs.py +78 -0
- pyfect/metric/_runtime.py +75 -0
- pyfect/metric/_snapshot.py +97 -0
- pyfect/metric/_state.py +74 -0
- pyfect/msgspec.py +98 -0
- pyfect/mutable_hash_map.py +140 -0
- pyfect/mutable_hash_set.py +95 -0
- pyfect/net/__init__.py +18 -0
- pyfect/net/_errors.py +29 -0
- pyfect/net/address_resolver.py +179 -0
- pyfect/net/dns/__init__.py +92 -0
- pyfect/net/dns/_error.py +43 -0
- pyfect/net/dns/_records.py +335 -0
- pyfect/net/dns/_service.py +287 -0
- pyfect/net/dns/_system.py +89 -0
- pyfect/net/host.py +311 -0
- pyfect/net/ip_interface.py +202 -0
- pyfect/net/ip_network.py +250 -0
- pyfect/net/net_address/__init__.py +245 -0
- pyfect/net/net_address/_inet.py +412 -0
- pyfect/net/net_address/_ip.py +348 -0
- pyfect/net/net_address/_mac.py +119 -0
- pyfect/optic/__init__.py +50 -0
- pyfect/optic/_builtins.py +90 -0
- pyfect/optic/_data.py +97 -0
- pyfect/optic/_optic.py +422 -0
- pyfect/optic/_steps.py +151 -0
- pyfect/option.py +362 -0
- pyfect/otel/__init__.py +22 -0
- pyfect/otel/_attributes.py +67 -0
- pyfect/otel/_producer.py +322 -0
- pyfect/otel/_shutdown.py +26 -0
- pyfect/otel/_span.py +292 -0
- pyfect/otel/_wrapper.py +92 -0
- pyfect/otel/logger.py +154 -0
- pyfect/otel/metrics.py +126 -0
- pyfect/otel/resource.py +111 -0
- pyfect/otel/sdk.py +195 -0
- pyfect/otel/tracer.py +157 -0
- pyfect/partitioned_semaphore.py +211 -0
- pyfect/path.py +304 -0
- pyfect/persistence/__init__.py +77 -0
- pyfect/persistence/_columns.py +32 -0
- pyfect/persistence/_error.py +14 -0
- pyfect/persistence/_seconds.py +68 -0
- pyfect/persistence/_sql_keys.py +47 -0
- pyfect/persistence/circuit_breaker/__init__.py +120 -0
- pyfect/persistence/circuit_breaker/_breaker.py +317 -0
- pyfect/persistence/circuit_breaker/_errors.py +67 -0
- pyfect/persistence/circuit_breaker/_memory.py +243 -0
- pyfect/persistence/circuit_breaker/_redis.py +509 -0
- pyfect/persistence/circuit_breaker/_store.py +60 -0
- pyfect/persistence/circuit_breaker/_types.py +191 -0
- pyfect/persistence/key_value_store/__init__.py +51 -0
- pyfect/persistence/key_value_store/_layers.py +216 -0
- pyfect/persistence/key_value_store/_schema_store.py +91 -0
- pyfect/persistence/key_value_store/_sql.py +197 -0
- pyfect/persistence/key_value_store/_store.py +212 -0
- pyfect/persistence/persistable.py +164 -0
- pyfect/persistence/persisted_cache.py +159 -0
- pyfect/persistence/persisted_queue/__init__.py +75 -0
- pyfect/persistence/persisted_queue/_error.py +42 -0
- pyfect/persistence/persisted_queue/_mailbox.py +101 -0
- pyfect/persistence/persisted_queue/_memory.py +182 -0
- pyfect/persistence/persisted_queue/_queue.py +272 -0
- pyfect/persistence/persisted_queue/_redis.py +455 -0
- pyfect/persistence/persisted_queue/_redis_scripts.py +395 -0
- pyfect/persistence/persisted_queue/_sql.py +334 -0
- pyfect/persistence/persisted_queue/_sql_migrations.py +319 -0
- pyfect/persistence/persisted_queue/_sql_statements.py +441 -0
- pyfect/persistence/persisted_queue/_store.py +147 -0
- pyfect/persistence/persistence/__init__.py +84 -0
- pyfect/persistence/persistence/_kvs.py +135 -0
- pyfect/persistence/persistence/_memory.py +62 -0
- pyfect/persistence/persistence/_redis.py +162 -0
- pyfect/persistence/persistence/_service.py +276 -0
- pyfect/persistence/persistence/_sql.py +499 -0
- pyfect/persistence/rate_limiter/__init__.py +82 -0
- pyfect/persistence/rate_limiter/_errors.py +59 -0
- pyfect/persistence/rate_limiter/_limiter.py +325 -0
- pyfect/persistence/rate_limiter/_memory.py +217 -0
- pyfect/persistence/rate_limiter/_redis.py +485 -0
- pyfect/persistence/rate_limiter/_store.py +89 -0
- pyfect/persistence/redis/__init__.py +52 -0
- pyfect/persistence/redis/_error.py +12 -0
- pyfect/persistence/redis/_make.py +234 -0
- pyfect/persistence/redis/_script.py +78 -0
- pyfect/persistence/redis/_service.py +104 -0
- pyfect/persistence/redis_py.py +303 -0
- pyfect/persistence/user_directories.py +195 -0
- pyfect/platform.py +26 -0
- pyfect/platform_error.py +256 -0
- pyfect/pool/__init__.py +36 -0
- pyfect/pool/_pool.py +707 -0
- pyfect/pool/_state.py +161 -0
- pyfect/pool/_ttl.py +105 -0
- pyfect/pool/_waiters.py +116 -0
- pyfect/primary_key.py +35 -0
- pyfect/process/__init__.py +82 -0
- pyfect/process/_child.py +213 -0
- pyfect/process/_command.py +358 -0
- pyfect/process/_handle.py +77 -0
- pyfect/process/_live.py +455 -0
- pyfect/process/_os.py +78 -0
- pyfect/process/_pipes.py +291 -0
- pyfect/process/_spawner.py +75 -0
- pyfect/pubsub/__init__.py +33 -0
- pyfect/pubsub/_atomic.py +181 -0
- pyfect/pubsub/_hub.py +214 -0
- pyfect/pubsub/_pubsub.py +214 -0
- pyfect/pubsub/_replay.py +107 -0
- pyfect/pubsub/_subscription.py +226 -0
- pyfect/pull.py +132 -0
- pyfect/py.typed +0 -0
- pyfect/pydantic.py +188 -0
- pyfect/queue/__init__.py +33 -0
- pyfect/queue/_queue.py +525 -0
- pyfect/queue/_state.py +111 -0
- pyfect/queue/_views.py +55 -0
- pyfect/random.py +146 -0
- pyfect/rc_map.py +363 -0
- pyfect/rc_ref.py +271 -0
- pyfect/reactivity/__init__.py +53 -0
- pyfect/reactivity/_async_result_schema.py +227 -0
- pyfect/reactivity/_hydration.py +18 -0
- pyfect/reactivity/_live.py +21 -0
- pyfect/reactivity/_reactivity.py +256 -0
- pyfect/reactivity/_result_effect.py +17 -0
- pyfect/reactivity/_time.py +11 -0
- pyfect/reactivity/async_result.py +679 -0
- pyfect/reactivity/atom/__init__.py +143 -0
- pyfect/reactivity/atom/_combinators.py +223 -0
- pyfect/reactivity/atom/_constructors.py +80 -0
- pyfect/reactivity/atom/_conversions.py +79 -0
- pyfect/reactivity/atom/_effect.py +252 -0
- pyfect/reactivity/atom/_fn.py +341 -0
- pyfect/reactivity/atom/_kvs.py +101 -0
- pyfect/reactivity/atom/_optimistic.py +162 -0
- pyfect/reactivity/atom/_runtime.py +323 -0
- pyfect/reactivity/atom/_serializable.py +65 -0
- pyfect/reactivity/atom/_subscription_ref.py +102 -0
- pyfect/reactivity/atom/_swr.py +69 -0
- pyfect/reactivity/atom/_types.py +435 -0
- pyfect/reactivity/atom_http_api.py +667 -0
- pyfect/reactivity/atom_ref.py +260 -0
- pyfect/reactivity/atom_registry/__init__.py +219 -0
- pyfect/reactivity/atom_registry/_batch.py +96 -0
- pyfect/reactivity/atom_registry/_dispatcher.py +60 -0
- pyfect/reactivity/atom_registry/_lifetime.py +202 -0
- pyfect/reactivity/atom_registry/_node.py +423 -0
- pyfect/reactivity/atom_registry/_registry.py +380 -0
- pyfect/reactivity/atom_rpc.py +283 -0
- pyfect/reactivity/hydration.py +200 -0
- pyfect/redacted.py +83 -0
- pyfect/ref.py +129 -0
- pyfect/resource.py +98 -0
- pyfect/result.py +306 -0
- pyfect/rpc/__init__.py +64 -0
- pyfect/rpc/_encoded_schema.py +86 -0
- pyfect/rpc/_json_rpc.py +246 -0
- pyfect/rpc/_methods.py +177 -0
- pyfect/rpc/_rpc.py +498 -0
- pyfect/rpc/client/__init__.py +100 -0
- pyfect/rpc/client/_core.py +449 -0
- pyfect/rpc/client/_http.py +257 -0
- pyfect/rpc/client/_protocol.py +171 -0
- pyfect/rpc/client/_serialized.py +321 -0
- pyfect/rpc/client/_transports.py +328 -0
- pyfect/rpc/client/_worker.py +283 -0
- pyfect/rpc/client_error.py +35 -0
- pyfect/rpc/group.py +296 -0
- pyfect/rpc/message.py +299 -0
- pyfect/rpc/middleware.py +182 -0
- pyfect/rpc/schema.py +89 -0
- pyfect/rpc/serialization.py +366 -0
- pyfect/rpc/server/__init__.py +181 -0
- pyfect/rpc/server/_core.py +549 -0
- pyfect/rpc/server/_protocol.py +123 -0
- pyfect/rpc/server/_serialized.py +363 -0
- pyfect/rpc/server/_stdio.py +101 -0
- pyfect/rpc/server/_transports.py +427 -0
- pyfect/rpc/server/_worker.py +108 -0
- pyfect/rpc/test.py +74 -0
- pyfect/rpc/worker.py +92 -0
- pyfect/run.py +393 -0
- pyfect/schedule/__init__.py +91 -0
- pyfect/schedule/_combinators.py +168 -0
- pyfect/schedule/_constructors.py +223 -0
- pyfect/schedule/_cron.py +49 -0
- pyfect/schedule/_drive.py +191 -0
- pyfect/schedule/_model.py +405 -0
- pyfect/scheduler.py +575 -0
- pyfect/schema/__init__.py +866 -0
- pyfect/schema/_adapters.py +39 -0
- pyfect/schema/_arbitrary_constraint.py +242 -0
- pyfect/schema/_arbitrary_graph.py +325 -0
- pyfect/schema/_arbitrary_links.py +179 -0
- pyfect/schema/_arbitrary_number.py +112 -0
- pyfect/schema/_arrays.py +287 -0
- pyfect/schema/_assignable.py +118 -0
- pyfect/schema/_boolean_literals.py +31 -0
- pyfect/schema/_checks_number.py +216 -0
- pyfect/schema/_checks_order.py +140 -0
- pyfect/schema/_checks_size.py +277 -0
- pyfect/schema/_checks_string.py +241 -0
- pyfect/schema/_class.py +569 -0
- pyfect/schema/_codec_derivation.py +227 -0
- pyfect/schema/_codec_links.py +221 -0
- pyfect/schema/_codecs.py +316 -0
- pyfect/schema/_collections.py +172 -0
- pyfect/schema/_constructor_default.py +89 -0
- pyfect/schema/_data.py +440 -0
- pyfect/schema/_data_codecs.py +145 -0
- pyfect/schema/_decimal_regexp.py +243 -0
- pyfect/schema/_declare.py +68 -0
- pyfect/schema/_derive.py +292 -0
- pyfect/schema/_duration_codecs.py +27 -0
- pyfect/schema/_ecma_pattern.py +416 -0
- pyfect/schema/_error.py +26 -0
- pyfect/schema/_error_instance.py +222 -0
- pyfect/schema/_graph.py +271 -0
- pyfect/schema/_instances.py +240 -0
- pyfect/schema/_jsonschema/__init__.py +90 -0
- pyfect/schema/_jsonschema/_annotations.py +114 -0
- pyfect/schema/_jsonschema/_checks.py +253 -0
- pyfect/schema/_jsonschema/_compile.py +346 -0
- pyfect/schema/_jsonschema/_exact.py +19 -0
- pyfect/schema/_jsonschema/_merge.py +211 -0
- pyfect/schema/_jsonschema/_objects.py +231 -0
- pyfect/schema/_jsonschema/_references.py +221 -0
- pyfect/schema/_jsonschema/_template.py +69 -0
- pyfect/schema/_literals.py +147 -0
- pyfect/schema/_middleware.py +209 -0
- pyfect/schema/_net.py +522 -0
- pyfect/schema/_options.py +49 -0
- pyfect/schema/_parse.py +351 -0
- pyfect/schema/_parsed_text.py +37 -0
- pyfect/schema/_primitives.py +48 -0
- pyfect/schema/_record.py +41 -0
- pyfect/schema/_refine.py +81 -0
- pyfect/schema/_schema.py +158 -0
- pyfect/schema/_semver.py +144 -0
- pyfect/schema/_strategy/__init__.py +258 -0
- pyfect/schema/_strategy/_big_decimal.py +203 -0
- pyfect/schema/_strategy/_composites.py +119 -0
- pyfect/schema/_strategy/_leaves.py +94 -0
- pyfect/schema/_strategy/_net.py +133 -0
- pyfect/schema/_struct.py +87 -0
- pyfect/schema/_struct_keys.py +128 -0
- pyfect/schema/_tagged.py +254 -0
- pyfect/schema/_template_literal.py +172 -0
- pyfect/schema/_to_codec_array_from_single.py +115 -0
- pyfect/schema/_to_codec_go_json.py +174 -0
- pyfect/schema/_to_codec_json.py +73 -0
- pyfect/schema/_to_codec_string_tree.py +115 -0
- pyfect/schema/_to_differ_json_patch.py +44 -0
- pyfect/schema/_to_encoder_xml.py +119 -0
- pyfect/schema/_to_equivalence.py +186 -0
- pyfect/schema/_to_formatter.py +180 -0
- pyfect/schema/_to_iso.py +73 -0
- pyfect/schema/_to_strategy.py +31 -0
- pyfect/schema/_transform.py +210 -0
- pyfect/schema/_tree.py +27 -0
- pyfect/schema/_type_walk.py +53 -0
- pyfect/schema/_undefined.py +58 -0
- pyfect/schema/_value_codecs.py +84 -0
- pyfect/schema/_values.py +123 -0
- pyfect/schema/_void.py +86 -0
- pyfect/schema/ast/__init__.py +227 -0
- pyfect/schema/ast/_annotations.py +43 -0
- pyfect/schema/ast/_arrays.py +210 -0
- pyfect/schema/ast/_base.py +295 -0
- pyfect/schema/ast/_candidates.py +309 -0
- pyfect/schema/ast/_check.py +181 -0
- pyfect/schema/ast/_class.py +159 -0
- pyfect/schema/ast/_constructors.py +65 -0
- pyfect/schema/ast/_declaration.py +97 -0
- pyfect/schema/ast/_encoding.py +111 -0
- pyfect/schema/ast/_json.py +129 -0
- pyfect/schema/ast/_keywords.py +125 -0
- pyfect/schema/ast/_literal.py +157 -0
- pyfect/schema/ast/_objects.py +356 -0
- pyfect/schema/ast/_record.py +36 -0
- pyfect/schema/ast/_result.py +116 -0
- pyfect/schema/ast/_steps.py +209 -0
- pyfect/schema/ast/_suspend.py +65 -0
- pyfect/schema/ast/_template_literal.py +256 -0
- pyfect/schema/ast/_union.py +172 -0
- pyfect/schema/getter/__init__.py +136 -0
- pyfect/schema/getter/_binary.py +104 -0
- pyfect/schema/getter/_coerce.py +196 -0
- pyfect/schema/getter/_getter.py +243 -0
- pyfect/schema/getter/_make.py +151 -0
- pyfect/schema/getter/_text.py +141 -0
- pyfect/schema/getter/_tree_record.py +170 -0
- pyfect/schema/issue.py +420 -0
- pyfect/schema/model/__init__.py +120 -0
- pyfect/schema/model/_class.py +245 -0
- pyfect/schema/model/_dates.py +103 -0
- pyfect/schema/model/_fields.py +77 -0
- pyfect/schema/model/_toolkit.py +114 -0
- pyfect/schema/model/_uuid.py +69 -0
- pyfect/schema/representation/__init__.py +424 -0
- pyfect/schema/representation/_builtin_revivers.py +331 -0
- pyfect/schema/representation/_code.py +106 -0
- pyfect/schema/representation/_code_builtins.py +281 -0
- pyfect/schema/representation/_code_document.py +563 -0
- pyfect/schema/representation/_code_sort.py +111 -0
- pyfect/schema/representation/_from.py +278 -0
- pyfect/schema/representation/_import/__init__.py +78 -0
- pyfect/schema/representation/_import/_finalize.py +121 -0
- pyfect/schema/representation/_import/_intersect.py +467 -0
- pyfect/schema/representation/_import/_js_order.py +66 -0
- pyfect/schema/representation/_import/_keywords.py +224 -0
- pyfect/schema/representation/_import/_nodes.py +199 -0
- pyfect/schema/representation/_import/_translate.py +455 -0
- pyfect/schema/representation/_json.py +520 -0
- pyfect/schema/representation/_json_schema.py +178 -0
- pyfect/schema/representation/_model.py +284 -0
- pyfect/schema/representation/_revivers.py +89 -0
- pyfect/schema/representation/_to.py +249 -0
- pyfect/schema/transformation/__init__.py +99 -0
- pyfect/schema/transformation/_builtins.py +314 -0
- pyfect/schema/transformation/_durations.py +150 -0
- pyfect/schema/transformation/_transformation.py +177 -0
- pyfect/schema/variant_schema/__init__.py +69 -0
- pyfect/schema/variant_schema/_class.py +188 -0
- pyfect/schema/variant_schema/_defaults.py +30 -0
- pyfect/schema/variant_schema/_field.py +32 -0
- pyfect/schema/variant_schema/_struct.py +130 -0
- pyfect/schema/variant_schema/_toolkit.py +160 -0
- pyfect/scope.py +374 -0
- pyfect/scoped_ref.py +124 -0
- pyfect/semaphore.py +276 -0
- pyfect/semver.py +403 -0
- pyfect/semver_range.py +420 -0
- pyfect/sink/__init__.py +131 -0
- pyfect/sink/_combinators.py +151 -0
- pyfect/sink/_constructors.py +187 -0
- pyfect/sink/_context.py +87 -0
- pyfect/sink/_folds.py +336 -0
- pyfect/sink/_for_each.py +87 -0
- pyfect/sink/_sink.py +265 -0
- pyfect/sink/_steps.py +34 -0
- pyfect/sink/_taking.py +183 -0
- pyfect/socket/__init__.py +146 -0
- pyfect/socket/_accepted.py +65 -0
- pyfect/socket/_connection.py +384 -0
- pyfect/socket/_errors.py +135 -0
- pyfect/socket/_loop.py +66 -0
- pyfect/socket/_net.py +281 -0
- pyfect/socket/_socket.py +245 -0
- pyfect/socket/_tls.py +88 -0
- pyfect/socket/_tls_session.py +117 -0
- pyfect/socket/_web_socket.py +555 -0
- pyfect/socket/datagram/__init__.py +110 -0
- pyfect/socket/datagram/_core.py +460 -0
- pyfect/socket/datagram/_errors.py +102 -0
- pyfect/socket/datagram/_live.py +675 -0
- pyfect/socket/datagram/_model.py +217 -0
- pyfect/socket/server.py +258 -0
- pyfect/socket/ws.py +484 -0
- pyfect/sql/__init__.py +60 -0
- pyfect/sql/_loop.py +134 -0
- pyfect/sql/clickhouse/__init__.py +74 -0
- pyfect/sql/clickhouse/_connection.py +237 -0
- pyfect/sql/clickhouse/_rows.py +87 -0
- pyfect/sql/clickhouse/client.py +322 -0
- pyfect/sql/clickhouse/config.py +241 -0
- pyfect/sql/clickhouse/driver.py +434 -0
- pyfect/sql/clickhouse/error.py +106 -0
- pyfect/sql/clickhouse/migrator.py +47 -0
- pyfect/sql/clickhouse/params.py +168 -0
- pyfect/sql/client/__init__.py +35 -0
- pyfect/sql/client/_client.py +309 -0
- pyfect/sql/client/_sqlite_acquirers.py +130 -0
- pyfect/sql/client/_transaction.py +410 -0
- pyfect/sql/connection.py +87 -0
- pyfect/sql/d1/__init__.py +48 -0
- pyfect/sql/d1/api.py +270 -0
- pyfect/sql/d1/client.py +344 -0
- pyfect/sql/d1/config.py +55 -0
- pyfect/sql/error.py +311 -0
- pyfect/sql/libsql/__init__.py +58 -0
- pyfect/sql/libsql/_connection.py +110 -0
- pyfect/sql/libsql/_hrana.py +258 -0
- pyfect/sql/libsql/_local.py +251 -0
- pyfect/sql/libsql/_targets.py +28 -0
- pyfect/sql/libsql/_values.py +46 -0
- pyfect/sql/libsql/client.py +225 -0
- pyfect/sql/libsql/config.py +170 -0
- pyfect/sql/libsql/migrator.py +43 -0
- pyfect/sql/libsql/sdk.py +123 -0
- pyfect/sql/migrator.py +434 -0
- pyfect/sql/model.py +324 -0
- pyfect/sql/mssql/__init__.py +79 -0
- pyfect/sql/mssql/_connection.py +169 -0
- pyfect/sql/mssql/_driver.py +103 -0
- pyfect/sql/mssql/_names.py +9 -0
- pyfect/sql/mssql/_pool.py +125 -0
- pyfect/sql/mssql/_tds.py +447 -0
- pyfect/sql/mssql/_tds_params.py +155 -0
- pyfect/sql/mssql/_worker.py +155 -0
- pyfect/sql/mssql/client.py +344 -0
- pyfect/sql/mssql/config.py +118 -0
- pyfect/sql/mssql/error.py +111 -0
- pyfect/sql/mssql/migrator.py +43 -0
- pyfect/sql/mssql/parameter.py +44 -0
- pyfect/sql/mssql/procedure.py +119 -0
- pyfect/sql/mssql/types.py +137 -0
- pyfect/sql/mysql/__init__.py +60 -0
- pyfect/sql/mysql/_asyncmy.py +168 -0
- pyfect/sql/mysql/_bind.py +76 -0
- pyfect/sql/mysql/_connection.py +156 -0
- pyfect/sql/mysql/_pool.py +63 -0
- pyfect/sql/mysql/_session.py +262 -0
- pyfect/sql/mysql/client.py +244 -0
- pyfect/sql/mysql/config.py +108 -0
- pyfect/sql/mysql/error.py +104 -0
- pyfect/sql/mysql/migrator.py +44 -0
- pyfect/sql/pg/__init__.py +82 -0
- pyfect/sql/pg/_resolve.py +284 -0
- pyfect/sql/pg/_session.py +380 -0
- pyfect/sql/pg/client.py +398 -0
- pyfect/sql/pg/config.py +142 -0
- pyfect/sql/pg/connection.py +585 -0
- pyfect/sql/pg/error.py +131 -0
- pyfect/sql/pg/migrator.py +139 -0
- pyfect/sql/pg/pool.py +265 -0
- pyfect/sql/resolver.py +294 -0
- pyfect/sql/result.py +24 -0
- pyfect/sql/schema.py +116 -0
- pyfect/sql/sqlite/__init__.py +26 -0
- pyfect/sql/sqlite/_connection.py +230 -0
- pyfect/sql/sqlite/_worker.py +71 -0
- pyfect/sql/sqlite/client.py +248 -0
- pyfect/sql/sqlite/migrator.py +43 -0
- pyfect/sql/statement/__init__.py +144 -0
- pyfect/sql/statement/_compiler.py +384 -0
- pyfect/sql/statement/_segments.py +487 -0
- pyfect/sql/statement/_statement.py +526 -0
- pyfect/sql/statement/_transforms.py +93 -0
- pyfect/sql/stream.py +88 -0
- pyfect/stdio.py +206 -0
- pyfect/stream/__init__.py +151 -0
- pyfect/stream/_async_iterable.py +129 -0
- pyfect/stream/_base.py +69 -0
- pyfect/stream/_bridge_methods.py +139 -0
- pyfect/stream/_catch.py +135 -0
- pyfect/stream/_catch_reason.py +558 -0
- pyfect/stream/_channel_methods.py +61 -0
- pyfect/stream/_chunk_methods.py +169 -0
- pyfect/stream/_chunking.py +310 -0
- pyfect/stream/_concat.py +65 -0
- pyfect/stream/_constructors.py +341 -0
- pyfect/stream/_context.py +147 -0
- pyfect/stream/_error_methods.py +254 -0
- pyfect/stream/_execution_plan.py +199 -0
- pyfect/stream/_fanout.py +154 -0
- pyfect/stream/_fanout_methods.py +183 -0
- pyfect/stream/_filter_methods.py +203 -0
- pyfect/stream/_grouping.py +168 -0
- pyfect/stream/_many.py +65 -0
- pyfect/stream/_map_methods.py +233 -0
- pyfect/stream/_mapping.py +126 -0
- pyfect/stream/_run.py +114 -0
- pyfect/stream/_run_methods.py +114 -0
- pyfect/stream/_service_methods.py +36 -0
- pyfect/stream/_sink_methods.py +151 -0
- pyfect/stream/_sinking.py +277 -0
- pyfect/stream/_sources.py +124 -0
- pyfect/stream/_stream.py +162 -0
- pyfect/stream/_taking.py +330 -0
- pyfect/stream/_text.py +43 -0
- pyfect/stream/_timing.py +294 -0
- pyfect/stream/_timing_methods.py +252 -0
- pyfect/stream/_zip_methods.py +279 -0
- pyfect/stream/_zipping.py +213 -0
- pyfect/subscription_ref.py +66 -0
- pyfect/synchronized_ref.py +38 -0
- pyfect/take.py +36 -0
- pyfect/terminal/__init__.py +41 -0
- pyfect/terminal/_keys.py +113 -0
- pyfect/terminal/_live.py +223 -0
- pyfect/terminal/_service.py +73 -0
- pyfect/testing/__init__.py +122 -0
- pyfect/testing/_assertions.py +92 -0
- pyfect/testing/_clock.py +309 -0
- pyfect/testing/_console.py +164 -0
- pyfect/testing/_crypto.py +44 -0
- pyfect/testing/_decorators.py +211 -0
- pyfect/testing/_flaky.py +34 -0
- pyfect/testing/_layers.py +153 -0
- pyfect/testing/_prop.py +255 -0
- pyfect/testing/_runners.py +84 -0
- pyfect/testing/_runtime.py +174 -0
- pyfect/testing/_schema.py +238 -0
- pyfect/testing/plugin.py +59 -0
- pyfect/tracer/__init__.py +107 -0
- pyfect/tracer/_model.py +176 -0
- pyfect/tracer/_native.py +172 -0
- pyfect/tracer/_refs.py +87 -0
- pyfect/tracer/_spans.py +269 -0
- pyfect/transaction.py +340 -0
- pyfect/trie.py +317 -0
- pyfect/tx_chunk.py +127 -0
- pyfect/tx_deferred.py +75 -0
- pyfect/tx_hash_map.py +265 -0
- pyfect/tx_hash_set.py +151 -0
- pyfect/tx_priority_queue.py +224 -0
- pyfect/tx_pubsub.py +194 -0
- pyfect/tx_queue/__init__.py +27 -0
- pyfect/tx_queue/_queue.py +359 -0
- pyfect/tx_queue/_views.py +60 -0
- pyfect/tx_reentrant_lock.py +201 -0
- pyfect/tx_ref.py +79 -0
- pyfect/tx_semaphore.py +136 -0
- pyfect/tx_subscription_ref.py +118 -0
- pyfect/workers/__init__.py +62 -0
- pyfect/workers/_frames.py +82 -0
- pyfect/workers/_mailbox.py +51 -0
- pyfect/workers/_port.py +106 -0
- pyfect/workers/_process.py +155 -0
- pyfect/workers/_thread.py +100 -0
- pyfect/workers/python_worker.py +133 -0
- pyfect/workers/python_worker_runner.py +112 -0
- pyfect/workers/transferable.py +103 -0
- pyfect/workers/worker.py +273 -0
- pyfect/workers/worker_error.py +86 -0
- pyfect/workers/worker_runner.py +85 -0
- pyfect/workflow/__init__.py +78 -0
- pyfect/workflow/_crypto.py +23 -0
- pyfect/workflow/_encoded.py +381 -0
- pyfect/workflow/_memory.py +424 -0
- pyfect/workflow/_result.py +43 -0
- pyfect/workflow/_services.py +241 -0
- pyfect/workflow/_suspension.py +153 -0
- pyfect/workflow/activity.py +427 -0
- pyfect/workflow/durable_clock.py +72 -0
- pyfect/workflow/durable_deferred.py +403 -0
- pyfect/workflow/durable_queue.py +259 -0
- pyfect/workflow/workflow.py +509 -0
- pyfect/workflow/workflow_engine.py +42 -0
- pyfect/workflow/workflow_proxy.py +161 -0
- pyfect/workflow/workflow_proxy_server.py +146 -0
- pyfect_dev-0.1.0.dist-info/METADATA +296 -0
- pyfect_dev-0.1.0.dist-info/RECORD +1265 -0
- pyfect_dev-0.1.0.dist-info/WHEEL +4 -0
- pyfect_dev-0.1.0.dist-info/entry_points.txt +3 -0
- pyfect_dev-0.1.0.dist-info/licenses/LICENSE +21 -0
pyfect/errors.py
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""Typed errors.
|
|
2
|
+
|
|
3
|
+
Subclass ``TaggedError`` for domain errors. One with fields must be a
|
|
4
|
+
``@dataclass(eq=False)`` (plain ``@dataclass`` works too, but pyright then
|
|
5
|
+
thinks it unhashable); constructing one whose fields nothing sets raises
|
|
6
|
+
``TypeError``. An
|
|
7
|
+
instance is yieldable: ``yield from NotFound(key)`` fails the surrounding
|
|
8
|
+
generator and adds ``NotFound`` to its error channel. Write
|
|
9
|
+
``return (yield from NotFound(key))`` so the type checker knows the code
|
|
10
|
+
after it is unreachable.
|
|
11
|
+
|
|
12
|
+
The class is the tag: ``catch_tag(NotFound, ...)`` matches by
|
|
13
|
+
``isinstance``, so catching a base class catches its subclasses, and the
|
|
14
|
+
types agree.
|
|
15
|
+
|
|
16
|
+
Like Effect's ``Data.TaggedError``, errors compare and hash structurally
|
|
17
|
+
(same class, equal fields or ``args``), a ``message`` field is the string
|
|
18
|
+
form, a ``cause`` field holding an exception becomes ``__cause__``, and
|
|
19
|
+
``exit_code`` is the process status ``run_main`` exits with when the error
|
|
20
|
+
ends the program. ``error_reported`` and the ``report_*`` attributes tune
|
|
21
|
+
how ``run_main`` and the error reporters (``pyfect.error_reporter``) treat
|
|
22
|
+
it.
|
|
23
|
+
"""
|
|
24
|
+
# pyright: reportPrivateUsage=false
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import builtins
|
|
29
|
+
import re
|
|
30
|
+
import sys
|
|
31
|
+
from collections.abc import Callable, Generator, Mapping
|
|
32
|
+
from dataclasses import dataclass, fields, is_dataclass
|
|
33
|
+
from types import MappingProxyType
|
|
34
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Final, Never, Self, TypeIs, get_origin, overload
|
|
35
|
+
|
|
36
|
+
from pyfect._core import Effect, _FailCause
|
|
37
|
+
from pyfect.cause import Cause, Fail, Interrupt, Reason, fail, from_reasons
|
|
38
|
+
from pyfect.duration import Seconds, format_duration
|
|
39
|
+
from pyfect.result import Err, Ok, Result
|
|
40
|
+
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
from pyfect.logger import Severity
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def with_cause(message: str, cause: object) -> str:
|
|
46
|
+
"""``message`` and why, for ``str()``: what a log line shows, while ``.message`` stays
|
|
47
|
+
Effect's text (docs/PARITY.md). A cause whose own message this one repeats replaces it,
|
|
48
|
+
so a wrapper around its reason says it once."""
|
|
49
|
+
if cause is None:
|
|
50
|
+
return message
|
|
51
|
+
if not isinstance(cause, BaseException):
|
|
52
|
+
return f"{message}: {cause!r}"
|
|
53
|
+
detail = str(cause)
|
|
54
|
+
if detail.startswith(message):
|
|
55
|
+
return detail
|
|
56
|
+
said = getattr(cause, "message", None)
|
|
57
|
+
if isinstance(said, str) and said and said in message:
|
|
58
|
+
return message.replace(said, detail, 1)
|
|
59
|
+
if detail and detail in message:
|
|
60
|
+
return message
|
|
61
|
+
return f"{message}: {type(cause).__name__}{f': {detail}' if detail else ''}"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _why(error: BaseException) -> object:
|
|
65
|
+
"""What caused ``error``: a ``reason`` that is an error (it tells its own cause), or else
|
|
66
|
+
its ``__cause__`` or ``cause``."""
|
|
67
|
+
reason = getattr(error, "reason", None)
|
|
68
|
+
if isinstance(reason, BaseException):
|
|
69
|
+
return reason
|
|
70
|
+
return error.__cause__ if error.__cause__ is not None else getattr(error, "cause", None)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TaggedError(Exception):
|
|
74
|
+
exit_code: ClassVar[int] = 1
|
|
75
|
+
error_reported: bool = True
|
|
76
|
+
"""``False``: ``run_main`` does not log this error when it ends the
|
|
77
|
+
program (Effect's ``Runtime.errorReported``)."""
|
|
78
|
+
report_ignored: bool = False
|
|
79
|
+
"""``True``: error reporters skip this error."""
|
|
80
|
+
report_severity: Severity | None = None
|
|
81
|
+
"""The severity error reporters give it (``None``: ``INFO``)."""
|
|
82
|
+
report_attributes: Mapping[str, object] = MappingProxyType({})
|
|
83
|
+
"""Extra metadata for error reporters."""
|
|
84
|
+
|
|
85
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
86
|
+
super().__init_subclass__(**kwargs)
|
|
87
|
+
# Put the structural methods in the subclass's own namespace: then
|
|
88
|
+
# ``@dataclass`` treats them as explicit and neither replaces
|
|
89
|
+
# ``__eq__`` nor sets ``__hash__ = None``. A class that defines (or
|
|
90
|
+
# inherits) its own ``__eq__`` or ``__hash__`` keeps it.
|
|
91
|
+
if cls.__eq__ is TaggedError.__eq__ and cls.__hash__ is TaggedError.__hash__:
|
|
92
|
+
type.__setattr__(cls, "__eq__", TaggedError.__eq__)
|
|
93
|
+
type.__setattr__(cls, "__hash__", TaggedError.__hash__)
|
|
94
|
+
# The bases are finished, so fields no constructor sets fail here; the
|
|
95
|
+
# class itself may still get ``@dataclass``, so it is checked when
|
|
96
|
+
# first constructed (``_check_fields_then_new``).
|
|
97
|
+
_require_dataclass_fields(cls, below=cls)
|
|
98
|
+
if "__init__" not in cls.__dict__ and _own_fields(cls):
|
|
99
|
+
type.__setattr__(cls, "__new__", staticmethod(_check_fields_then_new))
|
|
100
|
+
|
|
101
|
+
def _structure(self) -> tuple[object, ...]:
|
|
102
|
+
if is_dataclass(self):
|
|
103
|
+
return tuple(getattr(self, f.name) for f in fields(self) if f.compare)
|
|
104
|
+
return self.args
|
|
105
|
+
|
|
106
|
+
def __eq__(self, other: object) -> bool:
|
|
107
|
+
if not isinstance(other, TaggedError) or type(other) is not type(self):
|
|
108
|
+
return NotImplemented
|
|
109
|
+
return self._structure() == other._structure()
|
|
110
|
+
|
|
111
|
+
def __hash__(self) -> int:
|
|
112
|
+
return hash((type(self), self._structure()))
|
|
113
|
+
|
|
114
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
115
|
+
super().__setattr__(name, value)
|
|
116
|
+
if name != "cause":
|
|
117
|
+
return
|
|
118
|
+
if isinstance(value, BaseException):
|
|
119
|
+
self.__cause__ = value
|
|
120
|
+
elif value is None and self.__cause__ is not None:
|
|
121
|
+
# Assigning ``__cause__`` also sets ``__suppress_context__``; an
|
|
122
|
+
# absent cause must not hide the implicit ``__context__``.
|
|
123
|
+
self.__cause__ = None
|
|
124
|
+
self.__suppress_context__ = False
|
|
125
|
+
|
|
126
|
+
def __iter__(self) -> Generator[Effect[Never, Self, Never], Any, Never]:
|
|
127
|
+
yield _FailCause(fail(self))
|
|
128
|
+
raise AssertionError("unreachable: a failed effect never resumes")
|
|
129
|
+
|
|
130
|
+
def __str__(self) -> str:
|
|
131
|
+
message = getattr(self, "message", None)
|
|
132
|
+
if isinstance(message, str):
|
|
133
|
+
return with_cause(message, _why(self))
|
|
134
|
+
if is_dataclass(self):
|
|
135
|
+
return ", ".join(f"{f.name}={getattr(self, f.name)!r}" for f in fields(self))
|
|
136
|
+
return super().__str__()
|
|
137
|
+
|
|
138
|
+
def __reduce__(self) -> str | tuple[Any, ...]:
|
|
139
|
+
# A dataclass error's ``args`` need not match its fields, so copy and
|
|
140
|
+
# pickle rebuild it from the fields instead. The state is the rest
|
|
141
|
+
# (notes, ...): the constructor sets the fields, which may be frozen.
|
|
142
|
+
if is_dataclass(self):
|
|
143
|
+
init = {f.name: getattr(self, f.name) for f in fields(self) if f.init}
|
|
144
|
+
state = {k: v for k, v in self.__dict__.items() if k not in init}
|
|
145
|
+
return (_rebuild, (type(self), init), state)
|
|
146
|
+
return super().__reduce__()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _own_fields(cls: type) -> list[str]:
|
|
150
|
+
"""The instance fields ``cls`` itself annotates: not ``ClassVar`` /
|
|
151
|
+
``Final``, dunders or ``TaggedError``'s own attributes."""
|
|
152
|
+
if sys.version_info >= (3, 14):
|
|
153
|
+
import annotationlib
|
|
154
|
+
|
|
155
|
+
annotations = annotationlib.get_annotations(cls, format=annotationlib.Format.FORWARDREF)
|
|
156
|
+
else:
|
|
157
|
+
import inspect
|
|
158
|
+
|
|
159
|
+
annotations = inspect.get_annotations(cls)
|
|
160
|
+
return [
|
|
161
|
+
name
|
|
162
|
+
for name, annotation in annotations.items()
|
|
163
|
+
if not name.startswith("__")
|
|
164
|
+
and name not in TaggedError.__annotations__
|
|
165
|
+
and not _CLASS_LEVEL.match(annotation if isinstance(annotation, str) else repr(annotation))
|
|
166
|
+
and get_origin(annotation) not in (ClassVar, Final)
|
|
167
|
+
and annotation not in (ClassVar, Final)
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
_CLASS_LEVEL = re.compile(r"^(?:typing\.|t\.)?(?:ClassVar|Final)\b")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _require_dataclass_fields(cls: type, *, below: type | None = None) -> None:
|
|
175
|
+
"""Raise ``TypeError`` if a class between ``cls`` and ``TaggedError``
|
|
176
|
+
(above ``below``, when given) annotates fields that no constructor sets:
|
|
177
|
+
it is not a dataclass and neither it nor a subclass on the way to ``cls``
|
|
178
|
+
writes ``__init__``."""
|
|
179
|
+
handwritten_init = False
|
|
180
|
+
for klass in cls.__mro__:
|
|
181
|
+
if klass is TaggedError:
|
|
182
|
+
return
|
|
183
|
+
own_dataclass = "__dataclass_fields__" in klass.__dict__
|
|
184
|
+
if "__init__" in klass.__dict__ and not own_dataclass:
|
|
185
|
+
handwritten_init = True
|
|
186
|
+
if own_dataclass or handwritten_init or klass is below:
|
|
187
|
+
continue
|
|
188
|
+
names = _own_fields(klass)
|
|
189
|
+
if names:
|
|
190
|
+
raise TypeError(
|
|
191
|
+
f"{klass.__qualname__} declares fields ({', '.join(names)}) but is not a "
|
|
192
|
+
"dataclass, so nothing sets them; decorate it with @dataclass (or "
|
|
193
|
+
"@dataclass(eq=False)), or subclass S.TaggedError, which needs no decorator"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _check_fields_then_new(cls: type[TaggedError], *args: Any, **_kwargs: Any) -> Any:
|
|
198
|
+
"""The ``__new__`` of a class with fields and no ``__init__`` of its own:
|
|
199
|
+
the first time a class is built, check that ``@dataclass`` set them up.
|
|
200
|
+
The inherited ``__new__`` gets the positional arguments only, as
|
|
201
|
+
``BaseException.__new__`` keeps them (``OSError``'s refuses keywords
|
|
202
|
+
once a class overrides ``__new__``); ``__init__`` takes them all."""
|
|
203
|
+
new = cls.__dict__.get("_TaggedError__new")
|
|
204
|
+
if new is None:
|
|
205
|
+
_require_dataclass_fields(cls)
|
|
206
|
+
new = next(
|
|
207
|
+
own
|
|
208
|
+
for klass in cls.__mro__
|
|
209
|
+
if (own := klass.__dict__.get("__new__")) is not None and not _is_hook(own)
|
|
210
|
+
)
|
|
211
|
+
type.__setattr__(cls, "_TaggedError__new", new)
|
|
212
|
+
return new(cls, *args)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _is_hook(value: object) -> bool:
|
|
216
|
+
return isinstance(value, staticmethod) and value.__func__ is _check_fields_then_new
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _rebuild[T](cls: Callable[..., T], init: dict[str, Any]) -> T:
|
|
220
|
+
return cls(**init)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@dataclass
|
|
224
|
+
class TimeoutException(TaggedError, builtins.TimeoutError):
|
|
225
|
+
"""An effect ran longer than its ``timeout`` of ``duration``."""
|
|
226
|
+
|
|
227
|
+
duration: Seconds
|
|
228
|
+
|
|
229
|
+
def __str__(self) -> str:
|
|
230
|
+
return f"Operation timed out after '{format_duration(self.duration)}'"
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class NoSuchElementException(TaggedError, LookupError):
|
|
234
|
+
"""An expected value was missing."""
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class IllegalArgumentError(TaggedError, ValueError):
|
|
238
|
+
"""An argument outside what an operation accepts, such as an unknown
|
|
239
|
+
time zone name."""
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class Done[L = None](TaggedError):
|
|
243
|
+
"""A producer finished normally: no more items will come. ``value`` is
|
|
244
|
+
what it leaves over (Effect's ``Cause.Done(value)``); a bare ``Done``
|
|
245
|
+
means ``Done[None]``.
|
|
246
|
+
|
|
247
|
+
A queue that can be ended says so in its error type,
|
|
248
|
+
``Queue[int, Done]``; its takers fail with ``Done()`` once it drains."""
|
|
249
|
+
|
|
250
|
+
__match_args__ = ("value",)
|
|
251
|
+
|
|
252
|
+
@overload
|
|
253
|
+
def __init__(self: Done[None]) -> None: ...
|
|
254
|
+
@overload
|
|
255
|
+
def __init__(self, value: L) -> None: ...
|
|
256
|
+
def __init__(self, value: object = None) -> None:
|
|
257
|
+
super().__init__(value)
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
def value(self) -> L:
|
|
261
|
+
return self.args[0]
|
|
262
|
+
|
|
263
|
+
def __repr__(self) -> str:
|
|
264
|
+
return "Done()" if self.value is None else f"Done({self.value!r})"
|
|
265
|
+
|
|
266
|
+
__str__ = __repr__
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def filter_done[E](cause: Cause[E]) -> Result[Done[Any], Cause[E]]:
|
|
270
|
+
"""``Ok(done)`` if ``cause`` holds a ``Done`` failure and nothing else but
|
|
271
|
+
interruptions; otherwise ``Err`` of the cause without its ``Done``
|
|
272
|
+
failures (Effect's ``Pull.filterDone``)."""
|
|
273
|
+
dones = [r.error for r in cause.reasons if _is_done(r)]
|
|
274
|
+
if not dones:
|
|
275
|
+
return Err(cause)
|
|
276
|
+
if all(_is_done(r) or isinstance(r, Interrupt) for r in cause.reasons):
|
|
277
|
+
return Ok(dones[0])
|
|
278
|
+
return Err(from_reasons(r for r in cause.reasons if not _is_done(r)))
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _is_done(reason: Reason[object]) -> TypeIs[Fail[Done[Any]]]:
|
|
282
|
+
return isinstance(reason, Fail) and isinstance(reason.error, Done)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@dataclass(eq=False)
|
|
286
|
+
class AsyncFiberError(TaggedError):
|
|
287
|
+
"""``run_sync`` ran an effect that did not finish synchronously. The
|
|
288
|
+
fiber is left where it stopped."""
|
|
289
|
+
|
|
290
|
+
fiber: object
|
|
291
|
+
message: str = "An asynchronous Effect was executed with Effect.runSync"
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class ExceededCapacityError(TaggedError):
|
|
295
|
+
"""A bounded structure was asked to hold more than its capacity."""
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@dataclass(eq=False)
|
|
299
|
+
class UnknownError(TaggedError):
|
|
300
|
+
"""An exception raised by code given to ``try_`` without a ``catch``.
|
|
301
|
+
``cause`` is that exception, and also the error's ``__cause__``."""
|
|
302
|
+
|
|
303
|
+
cause: Exception
|
|
304
|
+
message: str | None = None
|
|
305
|
+
|
|
306
|
+
def __str__(self) -> str:
|
|
307
|
+
return str(self.cause) if self.message is None else with_cause(self.message, self.cause)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
__all__ = [
|
|
311
|
+
"AsyncFiberError",
|
|
312
|
+
"Done",
|
|
313
|
+
"ExceededCapacityError",
|
|
314
|
+
"IllegalArgumentError",
|
|
315
|
+
"NoSuchElementException",
|
|
316
|
+
"TaggedError",
|
|
317
|
+
"TimeoutException",
|
|
318
|
+
"UnknownError",
|
|
319
|
+
"filter_done",
|
|
320
|
+
"with_cause",
|
|
321
|
+
]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Event sourcing with sync (Effect's ``effect/eventlog``).
|
|
2
|
+
|
|
3
|
+
``event`` and ``event_group`` declare events; ``event_log`` writes them to
|
|
4
|
+
an ``event_journal`` and replays them through handlers; ``event_row``
|
|
5
|
+
makes events that keep table rows. ``event_log_remote`` syncs a log with
|
|
6
|
+
a server (``event_log_server_encrypted`` stores batches it cannot read,
|
|
7
|
+
``event_log_server_unencrypted`` runs the handlers itself), over the RPCs
|
|
8
|
+
of ``event_log_message``, encrypted by ``event_log_encryption`` (the
|
|
9
|
+
``eventlog`` extra, ``cryptography``) and authenticated by
|
|
10
|
+
``event_log_session_auth``. ``sql_event_journal``,
|
|
11
|
+
``sql_event_log_server_encrypted`` and ``sql_event_log_server_unencrypted``
|
|
12
|
+
keep the journal and the server storages in a SQLite or PostgreSQL
|
|
13
|
+
database.
|
|
14
|
+
|
|
15
|
+
Import it as a submodule (``from pyfect import eventlog``): ``import pyfect``
|
|
16
|
+
does not load it, since its servers load ``pyfect.rpc``.
|
|
17
|
+
|
|
18
|
+
Payloads, entries and write and change frames are ``encoding.schema_binary``
|
|
19
|
+
frames with Effect's bytes.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from pyfect.eventlog import (
|
|
23
|
+
event,
|
|
24
|
+
event_group,
|
|
25
|
+
event_journal,
|
|
26
|
+
event_log,
|
|
27
|
+
event_log_encryption,
|
|
28
|
+
event_log_message,
|
|
29
|
+
event_log_remote,
|
|
30
|
+
event_log_server,
|
|
31
|
+
event_log_server_encrypted,
|
|
32
|
+
event_log_server_unencrypted,
|
|
33
|
+
event_log_session_auth,
|
|
34
|
+
event_row,
|
|
35
|
+
sql_event_journal,
|
|
36
|
+
sql_event_log_server_encrypted,
|
|
37
|
+
sql_event_log_server_unencrypted,
|
|
38
|
+
)
|
|
39
|
+
from pyfect.eventlog.event import Event, Write
|
|
40
|
+
from pyfect.eventlog.event_group import EventGroup
|
|
41
|
+
from pyfect.eventlog.event_journal import Entry, EventJournal, EventJournalError, RemoteEntry
|
|
42
|
+
from pyfect.eventlog.event_log import EventLog, Identity, Registry
|
|
43
|
+
from pyfect.eventlog.event_log_encryption import EventLogEncryption
|
|
44
|
+
from pyfect.eventlog.event_log_remote import EventLogRemote, EventLogRemoteError
|
|
45
|
+
from pyfect.eventlog.event_row import EventRow
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"Entry",
|
|
49
|
+
"Event",
|
|
50
|
+
"EventGroup",
|
|
51
|
+
"EventJournal",
|
|
52
|
+
"EventJournalError",
|
|
53
|
+
"EventLog",
|
|
54
|
+
"EventLogEncryption",
|
|
55
|
+
"EventLogRemote",
|
|
56
|
+
"EventLogRemoteError",
|
|
57
|
+
"EventRow",
|
|
58
|
+
"Identity",
|
|
59
|
+
"Registry",
|
|
60
|
+
"RemoteEntry",
|
|
61
|
+
"Write",
|
|
62
|
+
"event",
|
|
63
|
+
"event_group",
|
|
64
|
+
"event_journal",
|
|
65
|
+
"event_log",
|
|
66
|
+
"event_log_encryption",
|
|
67
|
+
"event_log_message",
|
|
68
|
+
"event_log_remote",
|
|
69
|
+
"event_log_server",
|
|
70
|
+
"event_log_server_encrypted",
|
|
71
|
+
"event_log_server_unencrypted",
|
|
72
|
+
"event_log_session_auth",
|
|
73
|
+
"event_row",
|
|
74
|
+
"sql_event_journal",
|
|
75
|
+
"sql_event_log_server_encrypted",
|
|
76
|
+
"sql_event_log_server_unencrypted",
|
|
77
|
+
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Which stored entries conflict with a batch of incoming ones (Effect's
|
|
2
|
+
``eventlog/internal/conflicts.ts``): those of the same event and primary
|
|
3
|
+
key with a greater id."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from collections.abc import Iterator, Sequence
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from pyfect.eventlog.event_journal import Entry, EntryWithConflicts
|
|
13
|
+
|
|
14
|
+
# Set during client replay only, so it never leaves the process: an
|
|
15
|
+
# attribute outside the entry's fields, so equality and encoding ignore it.
|
|
16
|
+
_ANCHOR = "_eventlog_anchor"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class _Anchor:
|
|
21
|
+
entry: Entry
|
|
22
|
+
index: int
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def set_anchor(entry: Entry, source: Entry, index: int) -> None:
|
|
26
|
+
"""Order and conflict-check ``entry`` as ``source``, after the
|
|
27
|
+
``index`` outputs written for it before."""
|
|
28
|
+
object.__setattr__(entry, _ANCHOR, _Anchor(source, index))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def anchor_entry(entry: Entry) -> Entry:
|
|
32
|
+
anchor: _Anchor | None = getattr(entry, _ANCHOR, None)
|
|
33
|
+
return entry if anchor is None else anchor.entry
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _anchor_key(entry: Entry) -> tuple[bytes, int]:
|
|
37
|
+
# By anchor id, then by write order among one anchor's outputs.
|
|
38
|
+
anchor: _Anchor | None = getattr(entry, _ANCHOR, None)
|
|
39
|
+
return (entry.id, 0) if anchor is None else (anchor.entry.id, anchor.index)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(slots=True)
|
|
43
|
+
class ConflictGroup:
|
|
44
|
+
event: str
|
|
45
|
+
primary_key: str
|
|
46
|
+
oldest: Entry
|
|
47
|
+
entries: list[Entry] = field(default_factory=list["Entry"])
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ConflictIndex:
|
|
51
|
+
"""A batch, ordered by anchor, and the stored entries of its keys."""
|
|
52
|
+
|
|
53
|
+
__slots__ = ("_stored", "batch", "oldest", "oldest_millis")
|
|
54
|
+
|
|
55
|
+
def __init__(self, batch: Sequence[Entry]) -> None:
|
|
56
|
+
self.batch = sorted(batch, key=_anchor_key)
|
|
57
|
+
self.oldest = anchor_entry(self.batch[0])
|
|
58
|
+
self.oldest_millis = self.oldest.created_at_millis
|
|
59
|
+
self._stored: dict[str, dict[str, ConflictGroup]] = {}
|
|
60
|
+
for entry in self.batch:
|
|
61
|
+
by_key = self._stored.setdefault(entry.event, {})
|
|
62
|
+
if entry.primary_key not in by_key:
|
|
63
|
+
by_key[entry.primary_key] = ConflictGroup(
|
|
64
|
+
entry.event, entry.primary_key, anchor_entry(entry)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def events(self) -> list[str]:
|
|
69
|
+
return list(self._stored)
|
|
70
|
+
|
|
71
|
+
def groups(self) -> Iterator[ConflictGroup]:
|
|
72
|
+
for by_key in self._stored.values():
|
|
73
|
+
yield from by_key.values()
|
|
74
|
+
|
|
75
|
+
def add(self, entry: Entry) -> None:
|
|
76
|
+
"""Record a stored entry (ignored unless the batch has its key)."""
|
|
77
|
+
group = self._stored.get(entry.event, {}).get(entry.primary_key)
|
|
78
|
+
if group is not None:
|
|
79
|
+
group.entries.append(entry)
|
|
80
|
+
|
|
81
|
+
def to_batch(self) -> list[EntryWithConflicts]:
|
|
82
|
+
from pyfect.eventlog.event_journal import EntryWithConflicts
|
|
83
|
+
|
|
84
|
+
for group in self.groups():
|
|
85
|
+
group.entries.sort(key=lambda entry: entry.id)
|
|
86
|
+
result: list[EntryWithConflicts] = []
|
|
87
|
+
for entry in self.batch:
|
|
88
|
+
anchor = anchor_entry(entry).id
|
|
89
|
+
stored = self._stored[entry.event][entry.primary_key].entries
|
|
90
|
+
result.append(EntryWithConflicts(entry, tuple(s for s in stored if s.id > anchor)))
|
|
91
|
+
return result
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
__all__ = ["ConflictGroup", "ConflictIndex", "anchor_entry", "set_anchor"]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""AES-GCM and Ed25519 on the ``cryptography`` package (the extra
|
|
2
|
+
``pyfect-dev[eventlog]``), where Effect uses WebCrypto. Imported by the
|
|
3
|
+
functions that need it, so the rest of ``pyfect.eventlog`` works without
|
|
4
|
+
the extra."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pyfect._extras import missing_extra
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from cryptography.exceptions import InvalidSignature
|
|
12
|
+
from cryptography.hazmat.primitives import serialization
|
|
13
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
|
|
14
|
+
Ed25519PrivateKey,
|
|
15
|
+
Ed25519PublicKey,
|
|
16
|
+
)
|
|
17
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
18
|
+
except ImportError as error: # pragma: no cover - depends on the environment
|
|
19
|
+
raise missing_extra("pyfect.eventlog encryption and session auth", "eventlog") from error
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def ed25519_public_key(seed: bytes) -> bytes:
|
|
23
|
+
"""The raw public key of the private key ``seed``."""
|
|
24
|
+
public = Ed25519PrivateKey.from_private_bytes(seed).public_key()
|
|
25
|
+
return public.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def ed25519_sign(pkcs8: bytes, payload: bytes) -> bytes:
|
|
29
|
+
"""``payload`` signed with a PKCS#8 private key; ``ValueError`` when
|
|
30
|
+
the key is not an Ed25519 key."""
|
|
31
|
+
key = serialization.load_der_private_key(pkcs8, None)
|
|
32
|
+
if not isinstance(key, Ed25519PrivateKey):
|
|
33
|
+
raise ValueError("not an Ed25519 private key")
|
|
34
|
+
return key.sign(payload)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def ed25519_verify(public_key: bytes, signature: bytes, payload: bytes) -> bool:
|
|
38
|
+
"""Whether ``signature`` signs ``payload`` for the raw ``public_key``."""
|
|
39
|
+
key = Ed25519PublicKey.from_public_bytes(public_key)
|
|
40
|
+
try:
|
|
41
|
+
key.verify(signature, payload)
|
|
42
|
+
except InvalidSignature:
|
|
43
|
+
return False
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def aes_gcm_encrypt(key: bytes, iv: bytes, data: bytes, additional_data: bytes) -> bytes:
|
|
48
|
+
"""The ciphertext followed by its 128-bit tag, as WebCrypto's."""
|
|
49
|
+
return AESGCM(key).encrypt(iv, data, additional_data)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def aes_gcm_decrypt(key: bytes, iv: bytes, data: bytes, additional_data: bytes) -> bytes:
|
|
53
|
+
"""The plaintext; ``cryptography.exceptions.InvalidTag`` when ``data``
|
|
54
|
+
was altered or the key, iv or additional data differ."""
|
|
55
|
+
return AESGCM(key).decrypt(iv, data, additional_data)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
__all__ = [
|
|
59
|
+
"aes_gcm_decrypt",
|
|
60
|
+
"aes_gcm_encrypt",
|
|
61
|
+
"ed25519_public_key",
|
|
62
|
+
"ed25519_sign",
|
|
63
|
+
"ed25519_verify",
|
|
64
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""The identity an event log writes as (Effect's ``EventLog.Identity``)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from pyfect.context import Tag
|
|
8
|
+
from pyfect.redacted import Redacted
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True, eq=False)
|
|
12
|
+
class IdentityService:
|
|
13
|
+
"""A public key naming the identity and the private root secret its
|
|
14
|
+
encryption and signing keys are derived from. Compared by identity,
|
|
15
|
+
so derived keys are memoized per instance."""
|
|
16
|
+
|
|
17
|
+
public_key: str
|
|
18
|
+
private_key: Redacted[bytes]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Identity(Tag[IdentityService], key="effect/eventlog/EventLog/Identity"):
|
|
22
|
+
"""The identity of the event log, or of the client a server handler
|
|
23
|
+
serves."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__all__ = ["Identity", "IdentityService"]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Keys derived from an identity's root secret (Effect's
|
|
2
|
+
``eventlog/internal/identityRootSecretDerivation.ts``): SHA-256 of a
|
|
3
|
+
versioned, domain-separated label, a zero byte and the secret gives the
|
|
4
|
+
AES-GCM key and the Ed25519 seed."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import weakref
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
from pyfect._core import Effect
|
|
14
|
+
from pyfect._js import text_encode
|
|
15
|
+
from pyfect.effect import sync
|
|
16
|
+
from pyfect.eventlog._identity import IdentityService
|
|
17
|
+
from pyfect.redacted import Redacted
|
|
18
|
+
from pyfect.redacted import make as redacted
|
|
19
|
+
from pyfect.redacted import value as reveal
|
|
20
|
+
|
|
21
|
+
ED25519_PUBLIC_KEY_LENGTH = 32
|
|
22
|
+
ED25519_PKCS8_SEED_PREFIX = bytes.fromhex("302e020100300506032b657004220420")
|
|
23
|
+
ENCRYPTION_DERIVATION_LABEL_V1 = "effect/eventlog/identity/v1/encryption"
|
|
24
|
+
SIGNING_DERIVATION_LABEL_V1 = "effect/eventlog/identity/v1/signing"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True, slots=True)
|
|
28
|
+
class IdentityRootSecretMaterial:
|
|
29
|
+
"""The AES-GCM key bytes and the Ed25519 key pair (the private key as PKCS#8
|
|
30
|
+
bytes) of an identity."""
|
|
31
|
+
|
|
32
|
+
encryption_key_material: bytes
|
|
33
|
+
signing_public_key: bytes
|
|
34
|
+
signing_private_key: Redacted[bytes]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def derive_secret_bytes(root_secret: bytes, label: str) -> bytes:
|
|
38
|
+
return hashlib.sha256(text_encode(label) + b"\x00" + root_secret).digest()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def derive_identity_root_secret_material(root_secret: bytes) -> IdentityRootSecretMaterial:
|
|
42
|
+
from pyfect.eventlog import _crypto
|
|
43
|
+
|
|
44
|
+
encryption_key = derive_secret_bytes(root_secret, ENCRYPTION_DERIVATION_LABEL_V1)
|
|
45
|
+
seed = derive_secret_bytes(root_secret, SIGNING_DERIVATION_LABEL_V1)
|
|
46
|
+
public_key = _crypto.ed25519_public_key(seed)
|
|
47
|
+
return IdentityRootSecretMaterial(
|
|
48
|
+
encryption_key, public_key, redacted(ED25519_PKCS8_SEED_PREFIX + seed)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
type GetMaterial = Callable[[IdentityService], Effect[IdentityRootSecretMaterial]]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def make_get_identity_root_secret_material() -> GetMaterial:
|
|
56
|
+
"""The material of an identity, derived once per identity instance."""
|
|
57
|
+
cache: weakref.WeakKeyDictionary[IdentityService, IdentityRootSecretMaterial] = (
|
|
58
|
+
weakref.WeakKeyDictionary()
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def get(identity: IdentityService) -> IdentityRootSecretMaterial:
|
|
62
|
+
found = cache.get(identity)
|
|
63
|
+
if found is None:
|
|
64
|
+
found = cache[identity] = derive_identity_root_secret_material(
|
|
65
|
+
reveal(identity.private_key)
|
|
66
|
+
)
|
|
67
|
+
return found
|
|
68
|
+
|
|
69
|
+
return lambda identity: sync(lambda: get(identity))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
__all__ = [
|
|
73
|
+
"ENCRYPTION_DERIVATION_LABEL_V1",
|
|
74
|
+
"SIGNING_DERIVATION_LABEL_V1",
|
|
75
|
+
"IdentityRootSecretMaterial",
|
|
76
|
+
"derive_identity_root_secret_material",
|
|
77
|
+
"derive_secret_bytes",
|
|
78
|
+
"make_get_identity_root_secret_material",
|
|
79
|
+
]
|