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,211 @@
|
|
|
1
|
+
"""``@effect`` and ``@live``: pytest tests written as effects."""
|
|
2
|
+
# pyright: reportPrivateUsage=false
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import functools
|
|
7
|
+
from collections.abc import Callable, Generator
|
|
8
|
+
from typing import Any, Concatenate, Literal, overload
|
|
9
|
+
|
|
10
|
+
from pyfect._core import Effect, _Gen, _Suspend
|
|
11
|
+
from pyfect.clock import Clock, LiveClock
|
|
12
|
+
from pyfect.console import CurrentConsole
|
|
13
|
+
from pyfect.context import Context
|
|
14
|
+
from pyfect.duration import Duration, Seconds, to_seconds
|
|
15
|
+
from pyfect.run import unwrap
|
|
16
|
+
from pyfect.scope import Scope, scoped
|
|
17
|
+
from pyfect.testing._clock import TestClock
|
|
18
|
+
from pyfect.testing._console import TestConsole
|
|
19
|
+
from pyfect.testing._layers import Services
|
|
20
|
+
from pyfect.testing._runtime import AsyncioRuntime, Runtime, SyncRuntime
|
|
21
|
+
|
|
22
|
+
type TestBody[R] = Generator[Effect[Any, Any, R], Any, object] | Effect[object, object, R]
|
|
23
|
+
"""What a test function gives: a generator of effects (``yield from``), or
|
|
24
|
+
an effect. Any typed error fails the test."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Settings:
|
|
28
|
+
"""Defaults the pytest plugin reads from the ini file."""
|
|
29
|
+
|
|
30
|
+
timeout: Seconds | None = Seconds(5.0)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
settings = Settings()
|
|
34
|
+
|
|
35
|
+
type _Mode = Literal["effect", "live"]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class _Options:
|
|
39
|
+
__slots__ = ("asyncio", "mode", "timeout", "warning_delay")
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self, mode: _Mode, timeout: Duration | None, asyncio: bool, warning_delay: Duration
|
|
43
|
+
) -> None:
|
|
44
|
+
self.mode: _Mode = mode
|
|
45
|
+
self.timeout = None if timeout is None else to_seconds(timeout)
|
|
46
|
+
self.asyncio = asyncio
|
|
47
|
+
self.warning_delay = warning_delay
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TestDecorator:
|
|
51
|
+
"""``@effect(...)`` / ``@live(...)`` with options."""
|
|
52
|
+
|
|
53
|
+
__test__ = False
|
|
54
|
+
__slots__ = ("_options",)
|
|
55
|
+
|
|
56
|
+
def __init__(self, options: _Options) -> None:
|
|
57
|
+
self._options = options
|
|
58
|
+
|
|
59
|
+
@overload
|
|
60
|
+
def __call__[RO, **P](
|
|
61
|
+
self, f: Callable[Concatenate[Services[RO], P], TestBody[RO | Scope]], /
|
|
62
|
+
) -> Callable[Concatenate[Services[RO], P], None]: ...
|
|
63
|
+
@overload
|
|
64
|
+
def __call__[S, RO, **P](
|
|
65
|
+
self, f: Callable[Concatenate[S, Services[RO], P], TestBody[RO | Scope]], /
|
|
66
|
+
) -> Callable[Concatenate[S, Services[RO], P], None]: ...
|
|
67
|
+
@overload
|
|
68
|
+
def __call__[**P](self, f: Callable[P, TestBody[Scope]], /) -> Callable[P, None]: ...
|
|
69
|
+
def __call__(self, f: Callable[..., Any], /) -> Callable[..., None]:
|
|
70
|
+
return _make_test(f, self._options)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@overload
|
|
74
|
+
def effect[RO, **P](
|
|
75
|
+
f: Callable[Concatenate[Services[RO], P], TestBody[RO | Scope]], /
|
|
76
|
+
) -> Callable[Concatenate[Services[RO], P], None]: ...
|
|
77
|
+
@overload
|
|
78
|
+
def effect[S, RO, **P](
|
|
79
|
+
f: Callable[Concatenate[S, Services[RO], P], TestBody[RO | Scope]], /
|
|
80
|
+
) -> Callable[Concatenate[S, Services[RO], P], None]: ...
|
|
81
|
+
@overload
|
|
82
|
+
def effect[**P](f: Callable[P, TestBody[Scope]], /) -> Callable[P, None]: ...
|
|
83
|
+
@overload
|
|
84
|
+
def effect(
|
|
85
|
+
*, timeout: Duration | None = None, asyncio: bool = False, warning_delay: Duration = 1.0
|
|
86
|
+
) -> TestDecorator: ...
|
|
87
|
+
def effect(
|
|
88
|
+
f: Callable[..., Any] | None = None,
|
|
89
|
+
/,
|
|
90
|
+
*,
|
|
91
|
+
timeout: Duration | None = None,
|
|
92
|
+
asyncio: bool = False,
|
|
93
|
+
warning_delay: Duration = 1.0,
|
|
94
|
+
) -> Any:
|
|
95
|
+
"""A pytest test written as an effect, run with a ``TestClock``, a
|
|
96
|
+
``TestConsole`` and a ``Scope`` (closed when the test ends)::
|
|
97
|
+
|
|
98
|
+
@effect
|
|
99
|
+
def test_retries():
|
|
100
|
+
fiber = yield from flaky_call.retry(spaced(1)).fork()
|
|
101
|
+
yield from adjust(3)
|
|
102
|
+
assert (yield from fiber.join()) == "ok"
|
|
103
|
+
|
|
104
|
+
``timeout`` (real time; default from the ``pyfect_timeout`` ini option)
|
|
105
|
+
interrupts the test and runs its finalizers. ``asyncio`` runs it on an
|
|
106
|
+
event loop, for ``from_async``. A first parameter annotated
|
|
107
|
+
``Services[...]`` (a ``layer_fixture``; in a test class, the parameter
|
|
108
|
+
after ``self``) provides those services."""
|
|
109
|
+
decorator = TestDecorator(_Options("effect", timeout, asyncio, warning_delay))
|
|
110
|
+
return decorator if f is None else decorator(f)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@overload
|
|
114
|
+
def live[RO, **P](
|
|
115
|
+
f: Callable[Concatenate[Services[RO], P], TestBody[RO | Scope]], /
|
|
116
|
+
) -> Callable[Concatenate[Services[RO], P], None]: ...
|
|
117
|
+
@overload
|
|
118
|
+
def live[S, RO, **P](
|
|
119
|
+
f: Callable[Concatenate[S, Services[RO], P], TestBody[RO | Scope]], /
|
|
120
|
+
) -> Callable[Concatenate[S, Services[RO], P], None]: ...
|
|
121
|
+
@overload
|
|
122
|
+
def live[**P](f: Callable[P, TestBody[Scope]], /) -> Callable[P, None]: ...
|
|
123
|
+
@overload
|
|
124
|
+
def live(*, timeout: Duration | None = None, asyncio: bool = False) -> TestDecorator: ...
|
|
125
|
+
def live(
|
|
126
|
+
f: Callable[..., Any] | None = None,
|
|
127
|
+
/,
|
|
128
|
+
*,
|
|
129
|
+
timeout: Duration | None = None,
|
|
130
|
+
asyncio: bool = False,
|
|
131
|
+
) -> Any:
|
|
132
|
+
"""Like ``effect``, but with the live clock and console: sleeps take
|
|
133
|
+
real time and output is written."""
|
|
134
|
+
decorator = TestDecorator(_Options("live", timeout, asyncio, 1.0))
|
|
135
|
+
return decorator if f is None else decorator(f)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _make_test(f: Callable[..., Any], options: _Options) -> Callable[..., None]:
|
|
139
|
+
import pytest
|
|
140
|
+
|
|
141
|
+
@functools.wraps(f)
|
|
142
|
+
def test(*args: Any, **kwargs: Any) -> None:
|
|
143
|
+
services = _services_in(args, kwargs)
|
|
144
|
+
runtime = _runtime_for(services, options)
|
|
145
|
+
context, clock = _context_for(services, options)
|
|
146
|
+
timeout = options.timeout if options.timeout is not None else settings.timeout
|
|
147
|
+
try:
|
|
148
|
+
ex = runtime.run(scoped(_body(f, args, kwargs)), context, timeout=timeout)
|
|
149
|
+
finally:
|
|
150
|
+
if clock is not None:
|
|
151
|
+
clock.stop_warning()
|
|
152
|
+
if services is None:
|
|
153
|
+
runtime.close()
|
|
154
|
+
unwrap(ex)
|
|
155
|
+
|
|
156
|
+
marker = pytest.mark.pyfect_live if options.mode == "live" else pytest.mark.pyfect_effect
|
|
157
|
+
return marker(test)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _services_in(args: tuple[Any, ...], kwargs: dict[str, Any]) -> Services[Any] | None:
|
|
161
|
+
found: list[Services[Any]] = [a for a in (*args, *kwargs.values()) if isinstance(a, Services)]
|
|
162
|
+
if len(found) > 1:
|
|
163
|
+
raise TypeError(
|
|
164
|
+
"a test can use one layer_fixture; merge the layers into one with layer.merge"
|
|
165
|
+
)
|
|
166
|
+
return found[0] if found else None
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _runtime_for(services: Services[Any] | None, options: _Options) -> Runtime:
|
|
170
|
+
if services is None:
|
|
171
|
+
return AsyncioRuntime() if options.asyncio else SyncRuntime()
|
|
172
|
+
if options.asyncio != isinstance(services.runtime, AsyncioRuntime):
|
|
173
|
+
raise TypeError(
|
|
174
|
+
"the test and its layer_fixture must agree on asyncio=; the test runs on "
|
|
175
|
+
"the fixture's runtime"
|
|
176
|
+
)
|
|
177
|
+
return services.runtime
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _context_for(
|
|
181
|
+
services: Services[Any] | None, options: _Options
|
|
182
|
+
) -> tuple[Context, TestClock | None]:
|
|
183
|
+
"""The test's context, and the ``TestClock`` made for this test alone
|
|
184
|
+
(with a ``TestConsole`` of its own).
|
|
185
|
+
|
|
186
|
+
Under a ``layer_fixture`` an ``effect`` test uses the fixture's clock:
|
|
187
|
+
its ``TestClock``, or the live clock of a ``live=True`` fixture."""
|
|
188
|
+
context = Context.empty() if services is None else services.context
|
|
189
|
+
if options.mode == "live":
|
|
190
|
+
return context.add(Clock, LiveClock()), None
|
|
191
|
+
if services is not None:
|
|
192
|
+
return context, None
|
|
193
|
+
clock = TestClock(warning_delay=options.warning_delay)
|
|
194
|
+
return context.add(Clock, clock).add(CurrentConsole, TestConsole()), clock
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _body(
|
|
198
|
+
f: Callable[..., Any], args: tuple[Any, ...], kwargs: dict[str, Any]
|
|
199
|
+
) -> Effect[Any, Any, Any]:
|
|
200
|
+
def start() -> Effect[Any, Any, Any]:
|
|
201
|
+
out = f(*args, **kwargs)
|
|
202
|
+
if isinstance(out, Effect):
|
|
203
|
+
return out # pyright: ignore[reportUnknownVariableType]
|
|
204
|
+
if isinstance(out, Generator):
|
|
205
|
+
return _Gen(lambda: out) # pyright: ignore[reportUnknownLambdaType]
|
|
206
|
+
raise TypeError(
|
|
207
|
+
f"{f.__qualname__} must be a generator (use `yield from`) or return an Effect, "
|
|
208
|
+
f"got {type(out).__name__}"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return _Suspend(start)
|
pyfect/testing/_flaky.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""``flaky``: retry a test effect that sometimes fails."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Never, overload
|
|
6
|
+
|
|
7
|
+
from pyfect._core import Effect
|
|
8
|
+
from pyfect._effect_args import require_effect
|
|
9
|
+
from pyfect.cause import Cause, Die, Fail, from_reasons
|
|
10
|
+
from pyfect.duration import Duration
|
|
11
|
+
from pyfect.effect import fail_cause
|
|
12
|
+
from pyfect.schedule import recurs
|
|
13
|
+
from pyfect.scope import Scope, scoped
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@overload
|
|
17
|
+
def flaky[A, E](eff: Effect[A, E, Scope], timeout: Duration = 30.0) -> Effect[A]: ...
|
|
18
|
+
@overload
|
|
19
|
+
def flaky[A, E, R](
|
|
20
|
+
eff: Effect[A, E, Scope | R], timeout: Duration = 30.0
|
|
21
|
+
) -> Effect[A, Never, R]: ...
|
|
22
|
+
def flaky(eff: Effect[Any, Any, Any], timeout: Duration = 30.0) -> Effect[Any, Any, Any]:
|
|
23
|
+
"""Retry ``eff`` (in a fresh scope each time) after any failure, defects
|
|
24
|
+
included, up to 10 times while less than ``timeout`` has passed. Like
|
|
25
|
+
Effect's ``flakyTest``, the last failure dies: its typed errors become
|
|
26
|
+
defects, kept with their tracebacks."""
|
|
27
|
+
require_effect(eff, "eff")
|
|
28
|
+
policy = recurs(10).up_to(duration=timeout)
|
|
29
|
+
attempts: Effect[Any, Any, Any] = scoped(eff).sandbox().retry(policy)
|
|
30
|
+
return attempts.catch_all(lambda cause: fail_cause(_all_defects(cause)))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _all_defects(cause: Cause[Any]) -> Cause[Never]:
|
|
34
|
+
return from_reasons(Die(r.error) if isinstance(r, Fail) else r for r in cause.reasons)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""``layer_fixture`` / ``effect_fixture``: build a layer or run a scoped
|
|
2
|
+
effect once per pytest scope and share the result."""
|
|
3
|
+
# pyright: reportPrivateUsage=false
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from collections.abc import Callable, Generator
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Literal, Never, final
|
|
10
|
+
|
|
11
|
+
from pyfect._effect_args import require_effect
|
|
12
|
+
from pyfect._layer_core import require_layer
|
|
13
|
+
from pyfect.clock import Clock
|
|
14
|
+
from pyfect.console import CurrentConsole
|
|
15
|
+
from pyfect.context import Context
|
|
16
|
+
from pyfect.duration import Duration, to_seconds
|
|
17
|
+
from pyfect.effect import Effect, provide_service
|
|
18
|
+
from pyfect.exit import Success
|
|
19
|
+
from pyfect.layer import Layer
|
|
20
|
+
from pyfect.run import unwrap
|
|
21
|
+
from pyfect.scope import CloseableScope, Scope
|
|
22
|
+
from pyfect.testing._clock import TestClock
|
|
23
|
+
from pyfect.testing._console import TestConsole
|
|
24
|
+
from pyfect.testing._runtime import AsyncioRuntime, Runtime, SyncRuntime
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from _pytest.fixtures import FixtureFunctionDefinition
|
|
28
|
+
|
|
29
|
+
type FixtureScope = Literal["function", "class", "module", "package", "session"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@final
|
|
33
|
+
class Services[RO]:
|
|
34
|
+
"""The services a ``layer_fixture`` built, and the runtime they live on.
|
|
35
|
+
|
|
36
|
+
Take it as the first parameter of an ``@effect`` test, annotated with
|
|
37
|
+
what the layer builds; the test may then use those services::
|
|
38
|
+
|
|
39
|
+
db = layer_fixture(DatabaseLive)
|
|
40
|
+
|
|
41
|
+
@effect
|
|
42
|
+
def test_query(db: Services[Database]):
|
|
43
|
+
conn = yield from Database()
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
__slots__ = ("context", "runtime")
|
|
47
|
+
|
|
48
|
+
def __init__(self, context: Context, runtime: Runtime) -> None:
|
|
49
|
+
self.context = context
|
|
50
|
+
self.runtime = runtime
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def layer_fixture[RO, E](
|
|
54
|
+
layer: Layer[RO, E],
|
|
55
|
+
*,
|
|
56
|
+
scope: FixtureScope = "module",
|
|
57
|
+
live: bool = False,
|
|
58
|
+
asyncio: bool = False,
|
|
59
|
+
warning_delay: Duration = 1.0,
|
|
60
|
+
timeout: Duration | None = None,
|
|
61
|
+
) -> FixtureFunctionDefinition:
|
|
62
|
+
"""A pytest fixture that builds ``layer`` once per ``scope`` and
|
|
63
|
+
releases its resources when the scope ends.
|
|
64
|
+
|
|
65
|
+
The layer is built with a ``TestClock`` and a ``TestConsole`` (shared
|
|
66
|
+
by every test that uses the fixture), unless ``live``: then it and its
|
|
67
|
+
``effect`` tests use the live clock and console. Tests using the fixture run on the
|
|
68
|
+
fixture's runtime, so fibers the layer forks keep running; ``asyncio``
|
|
69
|
+
picks an event loop for it, needed by ``from_async``. ``timeout``
|
|
70
|
+
bounds building and releasing, in real time."""
|
|
71
|
+
require_layer(layer, "layer")
|
|
72
|
+
|
|
73
|
+
def fixture() -> Generator[Services[RO]]:
|
|
74
|
+
with _held(
|
|
75
|
+
layer.build_with_scope,
|
|
76
|
+
live=live,
|
|
77
|
+
asyncio=asyncio,
|
|
78
|
+
warning_delay=warning_delay,
|
|
79
|
+
timeout=timeout,
|
|
80
|
+
) as (built, context, runtime):
|
|
81
|
+
yield Services(context.merge(built), runtime)
|
|
82
|
+
|
|
83
|
+
return _fixture(fixture, scope)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def effect_fixture[A, E](
|
|
87
|
+
eff: Effect[A, E, Scope],
|
|
88
|
+
*,
|
|
89
|
+
scope: FixtureScope = "function",
|
|
90
|
+
live: bool = False,
|
|
91
|
+
asyncio: bool = False,
|
|
92
|
+
warning_delay: Duration = 1.0,
|
|
93
|
+
timeout: Duration | None = None,
|
|
94
|
+
) -> FixtureFunctionDefinition:
|
|
95
|
+
"""A pytest fixture whose value is what ``eff`` succeeds with, run once
|
|
96
|
+
per ``scope``. Finalizers ``eff`` adds to its ``Scope`` run when the
|
|
97
|
+
scope ends; a failure is raised as the fixture's error.
|
|
98
|
+
|
|
99
|
+
The options are ``layer_fixture``'s. The value is a plain Python value,
|
|
100
|
+
so any test can take it; to share services or forked fibers with
|
|
101
|
+
``effect`` tests, use ``layer_fixture``."""
|
|
102
|
+
require_effect(eff, "eff")
|
|
103
|
+
|
|
104
|
+
def fixture() -> Generator[A]:
|
|
105
|
+
with _held(
|
|
106
|
+
lambda resources: eff.pipe(provide_service(Scope, resources)),
|
|
107
|
+
live=live,
|
|
108
|
+
asyncio=asyncio,
|
|
109
|
+
warning_delay=warning_delay,
|
|
110
|
+
timeout=timeout,
|
|
111
|
+
) as (value, _context, _runtime):
|
|
112
|
+
yield value
|
|
113
|
+
|
|
114
|
+
return _fixture(fixture, scope)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _fixture(
|
|
118
|
+
fixture: Callable[[], Generator[Any]], scope: FixtureScope
|
|
119
|
+
) -> FixtureFunctionDefinition:
|
|
120
|
+
import pytest
|
|
121
|
+
|
|
122
|
+
return pytest.fixture(scope=scope)(fixture)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@contextmanager
|
|
126
|
+
def _held[T](
|
|
127
|
+
build: Callable[[CloseableScope], Effect[T, Any, Never]],
|
|
128
|
+
*,
|
|
129
|
+
live: bool,
|
|
130
|
+
asyncio: bool,
|
|
131
|
+
warning_delay: Duration,
|
|
132
|
+
timeout: Duration | None,
|
|
133
|
+
) -> Generator[tuple[T, Context, Runtime]]:
|
|
134
|
+
"""Run ``build`` on a fresh runtime and clock, hold what it built, then
|
|
135
|
+
close its scope and the runtime."""
|
|
136
|
+
limit = None if timeout is None else to_seconds(timeout)
|
|
137
|
+
runtime: Runtime = AsyncioRuntime() if asyncio else SyncRuntime()
|
|
138
|
+
clock = None if live else TestClock(warning_delay=warning_delay)
|
|
139
|
+
context = (
|
|
140
|
+
Context.empty()
|
|
141
|
+
if clock is None
|
|
142
|
+
else Context.empty().add(Clock, clock).add(CurrentConsole, TestConsole())
|
|
143
|
+
)
|
|
144
|
+
resources = CloseableScope()
|
|
145
|
+
try:
|
|
146
|
+
built = unwrap(runtime.run(build(resources), context, timeout=limit))
|
|
147
|
+
yield built, context, runtime
|
|
148
|
+
finally:
|
|
149
|
+
closed = runtime.run(resources.close(Success(None)), context, timeout=limit)
|
|
150
|
+
if clock is not None:
|
|
151
|
+
clock.stop_warning()
|
|
152
|
+
runtime.close()
|
|
153
|
+
unwrap(closed)
|
pyfect/testing/_prop.py
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""Property tests over schemas and Hypothesis strategies (``@effect/vitest``'s
|
|
2
|
+
``it.prop``, ``it.effect.prop`` and ``it.live.prop``)::
|
|
3
|
+
|
|
4
|
+
@testing.prop(S.Finite, S.Int)
|
|
5
|
+
def test_symmetry(a: float, b: int) -> bool:
|
|
6
|
+
return a + b == b + a
|
|
7
|
+
|
|
8
|
+
@testing.effect_prop(text=S.String)
|
|
9
|
+
def test_echo(text: str):
|
|
10
|
+
yield from fx.Console.log(text)
|
|
11
|
+
|
|
12
|
+
Hypothesis runs the property: a schema is drawn with ``S.to_strategy``, a
|
|
13
|
+
strategy as it is. Positional inputs fill the rightmost parameters (as
|
|
14
|
+
``@given``'s do), keyword inputs the parameters they name; the others are
|
|
15
|
+
pytest fixtures. A property returning ``False`` or raising is falsified and
|
|
16
|
+
shrunk. An ``effect_prop`` / ``live_prop`` property is an effect test run
|
|
17
|
+
once per drawn input, each run with its own ``TestClock``, ``TestConsole``
|
|
18
|
+
and ``Scope``: a typed failure or a defect falsifies it, an interruption or
|
|
19
|
+
the ``timeout`` (real time, for the whole check) ends the check without
|
|
20
|
+
shrinking. ``runs`` is Hypothesis's ``max_examples`` and ``seed`` its seed;
|
|
21
|
+
unset, they come from the active Hypothesis settings profile (Effect's
|
|
22
|
+
``Arbitrary.configureGlobal``). Needs the ``pyfect-dev[hypothesis]`` extra.
|
|
23
|
+
"""
|
|
24
|
+
# pyright: reportPrivateUsage=false
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import functools
|
|
29
|
+
import inspect
|
|
30
|
+
import time
|
|
31
|
+
from collections.abc import Callable, Generator, Mapping
|
|
32
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
33
|
+
|
|
34
|
+
from pyfect._core import Effect
|
|
35
|
+
from pyfect._extras import missing_extra
|
|
36
|
+
from pyfect.duration import Duration, Seconds
|
|
37
|
+
from pyfect.exit import Failure, Success
|
|
38
|
+
from pyfect.run import unwrap
|
|
39
|
+
from pyfect.schema import Codec, to_strategy
|
|
40
|
+
from pyfect.scope import scoped
|
|
41
|
+
from pyfect.testing._decorators import (
|
|
42
|
+
TestBody,
|
|
43
|
+
_body,
|
|
44
|
+
_context_for,
|
|
45
|
+
_Mode,
|
|
46
|
+
_Options,
|
|
47
|
+
_runtime_for,
|
|
48
|
+
_services_in,
|
|
49
|
+
)
|
|
50
|
+
from pyfect.testing._decorators import settings as plugin_settings
|
|
51
|
+
|
|
52
|
+
if TYPE_CHECKING:
|
|
53
|
+
from hypothesis.strategies import SearchStrategy
|
|
54
|
+
|
|
55
|
+
type Arbitrary = Codec[Any, Any, Any, Any] | SearchStrategy[Any]
|
|
56
|
+
"""A property input: a schema (drawn with ``S.to_strategy``) or a Hypothesis
|
|
57
|
+
strategy (Effect's ``Arbitrary``)."""
|
|
58
|
+
|
|
59
|
+
type AnyCodec = Codec[Any, Any, Any, Any]
|
|
60
|
+
type Property = Callable[..., bool | None]
|
|
61
|
+
type _Decorator = Callable[[Callable[..., Any]], Callable[..., None]]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class PropertyFalsifiedError(AssertionError):
|
|
65
|
+
"""A property returned ``False``."""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class _EndCheck(BaseException):
|
|
69
|
+
"""Ends the whole check: Hypothesis shrinks on ``Exception``s and lets
|
|
70
|
+
other ``BaseException``s through."""
|
|
71
|
+
|
|
72
|
+
def __init__(self, error: BaseException) -> None:
|
|
73
|
+
super().__init__(error)
|
|
74
|
+
self.error = error
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def prop(
|
|
78
|
+
*arbitraries: Arbitrary,
|
|
79
|
+
runs: int | None = None,
|
|
80
|
+
seed: int | None = None,
|
|
81
|
+
**keyed: Arbitrary,
|
|
82
|
+
) -> Callable[[Property], Callable[..., None]]:
|
|
83
|
+
"""A pytest property test: ``f`` returns ``True`` / ``None`` to pass and
|
|
84
|
+
``False`` to falsify; an effect or generator is refused with
|
|
85
|
+
``TypeError`` (use ``effect_prop``)."""
|
|
86
|
+
|
|
87
|
+
def check(
|
|
88
|
+
f: Callable[..., Any], args: tuple[Any, ...], kwargs: dict[str, Any], started: float
|
|
89
|
+
) -> None:
|
|
90
|
+
out = f(*args, **kwargs)
|
|
91
|
+
if isinstance(out, Effect | Generator) or not (out is None or isinstance(out, bool)):
|
|
92
|
+
raise TypeError(
|
|
93
|
+
"testing.prop properties must return bool or None; "
|
|
94
|
+
"use testing.effect_prop for effects"
|
|
95
|
+
)
|
|
96
|
+
if out is False:
|
|
97
|
+
raise PropertyFalsifiedError(f"property {f.__qualname__} returned False")
|
|
98
|
+
|
|
99
|
+
return _property(arbitraries, keyed, runs, seed, check, mode=None)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def effect_prop(
|
|
103
|
+
*arbitraries: Arbitrary,
|
|
104
|
+
runs: int | None = None,
|
|
105
|
+
seed: int | None = None,
|
|
106
|
+
timeout: Duration | None = None,
|
|
107
|
+
asyncio: bool = False,
|
|
108
|
+
warning_delay: Duration = 1.0,
|
|
109
|
+
**keyed: Arbitrary,
|
|
110
|
+
) -> Callable[[Callable[..., TestBody[Any]]], Callable[..., None]]:
|
|
111
|
+
"""A property test written as an effect (``testing.effect`` per drawn
|
|
112
|
+
input): it fails with ``False``, a typed failure or a defect."""
|
|
113
|
+
options = _Options("effect", timeout, asyncio, warning_delay)
|
|
114
|
+
return _property(arbitraries, keyed, runs, seed, _effect_runner(options), mode="effect")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def live_prop(
|
|
118
|
+
*arbitraries: Arbitrary,
|
|
119
|
+
runs: int | None = None,
|
|
120
|
+
seed: int | None = None,
|
|
121
|
+
timeout: Duration | None = None,
|
|
122
|
+
asyncio: bool = False,
|
|
123
|
+
**keyed: Arbitrary,
|
|
124
|
+
) -> Callable[[Callable[..., TestBody[Any]]], Callable[..., None]]:
|
|
125
|
+
"""``effect_prop`` with the live clock and console."""
|
|
126
|
+
options = _Options("live", timeout, asyncio, 1.0)
|
|
127
|
+
return _property(arbitraries, keyed, runs, seed, _effect_runner(options), mode="live")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
type _Check = Callable[[Callable[..., Any], tuple[Any, ...], dict[str, Any], float], None]
|
|
131
|
+
"""Runs the property on one drawn input; ``started`` is when the check
|
|
132
|
+
began (``time.monotonic``)."""
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _property(
|
|
136
|
+
arbitraries: tuple[Arbitrary, ...],
|
|
137
|
+
keyed: Mapping[str, Arbitrary],
|
|
138
|
+
runs: int | None,
|
|
139
|
+
seed: int | None,
|
|
140
|
+
check: _Check,
|
|
141
|
+
*,
|
|
142
|
+
mode: _Mode | None,
|
|
143
|
+
) -> _Decorator:
|
|
144
|
+
"""``mode`` puts the plugin's ``pyfect_effect`` / ``pyfect_live``
|
|
145
|
+
marker on the test."""
|
|
146
|
+
import pytest
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
import hypothesis
|
|
150
|
+
from hypothesis.strategies import SearchStrategy
|
|
151
|
+
except ImportError as error:
|
|
152
|
+
raise missing_extra("testing.prop", "hypothesis") from error
|
|
153
|
+
|
|
154
|
+
def decorate(f: Callable[..., Any]) -> Callable[..., None]:
|
|
155
|
+
strategies = _strategies_for(f, arbitraries, keyed, SearchStrategy)
|
|
156
|
+
signature = inspect.signature(f)
|
|
157
|
+
fixtures = [p for name, p in signature.parameters.items() if name not in strategies]
|
|
158
|
+
|
|
159
|
+
@functools.wraps(f)
|
|
160
|
+
def run(*args: Any, **kwargs: Any) -> None:
|
|
161
|
+
started = time.monotonic()
|
|
162
|
+
|
|
163
|
+
@functools.wraps(f)
|
|
164
|
+
def sample(*args: Any, **kwargs: Any) -> None:
|
|
165
|
+
check(f, args, kwargs, started)
|
|
166
|
+
|
|
167
|
+
# Built per run, so unset options follow the profile loaded now.
|
|
168
|
+
options = hypothesis.settings(deadline=None)
|
|
169
|
+
if runs is not None:
|
|
170
|
+
options = hypothesis.settings(options, max_examples=runs)
|
|
171
|
+
test = options(hypothesis.given(**strategies)(sample))
|
|
172
|
+
if seed is not None:
|
|
173
|
+
test = hypothesis.seed(seed)(test)
|
|
174
|
+
try:
|
|
175
|
+
test(*args, **kwargs)
|
|
176
|
+
except _EndCheck as end:
|
|
177
|
+
raise end.error from None
|
|
178
|
+
|
|
179
|
+
run.__signature__ = signature.replace(parameters=fixtures) # pyright: ignore[reportAttributeAccessIssue]
|
|
180
|
+
match mode:
|
|
181
|
+
case "effect":
|
|
182
|
+
return pytest.mark.pyfect_effect(run)
|
|
183
|
+
case "live":
|
|
184
|
+
return pytest.mark.pyfect_live(run)
|
|
185
|
+
case None:
|
|
186
|
+
return run
|
|
187
|
+
|
|
188
|
+
return decorate
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _strategies_for(
|
|
192
|
+
f: Callable[..., Any],
|
|
193
|
+
arbitraries: tuple[Arbitrary, ...],
|
|
194
|
+
keyed: Mapping[str, Arbitrary],
|
|
195
|
+
strategy_type: type[SearchStrategy[Any]],
|
|
196
|
+
) -> dict[str, SearchStrategy[Any]]:
|
|
197
|
+
params = list(inspect.signature(f).parameters)
|
|
198
|
+
unknown = keyed.keys() - set(params)
|
|
199
|
+
if unknown:
|
|
200
|
+
raise TypeError(f"{f.__qualname__} has no parameter(s) {sorted(unknown)}")
|
|
201
|
+
free = [name for name in params if name not in keyed]
|
|
202
|
+
if len(arbitraries) > len(free):
|
|
203
|
+
raise TypeError(
|
|
204
|
+
f"{f.__qualname__} has {len(free)} parameter(s) left, "
|
|
205
|
+
f"too few for {len(arbitraries)} positional property inputs"
|
|
206
|
+
)
|
|
207
|
+
named = dict(zip(free[len(free) - len(arbitraries) :], arbitraries, strict=True))
|
|
208
|
+
return {
|
|
209
|
+
name: input if isinstance(input, strategy_type) else to_strategy(cast("AnyCodec", input))
|
|
210
|
+
for name, input in {**named, **keyed}.items()
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _effect_runner(options: _Options) -> _Check:
|
|
215
|
+
"""One effect test per drawn input; the timeout is for the whole check."""
|
|
216
|
+
|
|
217
|
+
def check(
|
|
218
|
+
f: Callable[..., Any], args: tuple[Any, ...], kwargs: dict[str, Any], started: float
|
|
219
|
+
) -> None:
|
|
220
|
+
limit = options.timeout if options.timeout is not None else plugin_settings.timeout
|
|
221
|
+
left = None if limit is None else Seconds(max(limit - (time.monotonic() - started), 0.0))
|
|
222
|
+
_run_sample(f, args, kwargs, options, left)
|
|
223
|
+
|
|
224
|
+
return check
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _run_sample(
|
|
228
|
+
f: Callable[..., Any],
|
|
229
|
+
args: tuple[Any, ...],
|
|
230
|
+
kwargs: dict[str, Any],
|
|
231
|
+
options: _Options,
|
|
232
|
+
timeout: Seconds | None,
|
|
233
|
+
) -> None:
|
|
234
|
+
services = _services_in(args, kwargs)
|
|
235
|
+
runtime = _runtime_for(services, options)
|
|
236
|
+
context, clock = _context_for(services, options)
|
|
237
|
+
try:
|
|
238
|
+
ex = runtime.run(scoped(_body(f, args, kwargs)), context, timeout=timeout)
|
|
239
|
+
except TimeoutError as error:
|
|
240
|
+
raise _EndCheck(error) from None
|
|
241
|
+
finally:
|
|
242
|
+
if clock is not None:
|
|
243
|
+
clock.stop_warning()
|
|
244
|
+
if services is None:
|
|
245
|
+
runtime.close()
|
|
246
|
+
match ex:
|
|
247
|
+
case Failure(cause=cause) if cause.has_interrupts:
|
|
248
|
+
raise _EndCheck(cause.raisable())
|
|
249
|
+
case Success(value=False):
|
|
250
|
+
raise PropertyFalsifiedError(f"property {f.__qualname__} returned False")
|
|
251
|
+
case _:
|
|
252
|
+
unwrap(ex)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
__all__ = ["Arbitrary", "PropertyFalsifiedError", "effect_prop", "live_prop", "prop"]
|