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,396 @@
|
|
|
1
|
+
"""Tar archives as streams (Effect's ``encoding/Tar``).
|
|
2
|
+
|
|
3
|
+
``decode`` streams entry bodies from the archive input and ``encode`` streams them
|
|
4
|
+
back into archive bytes; neither holds a whole body, which is what these channels
|
|
5
|
+
add to the blocking ``tarfile``. The decoder reads v7, USTAR, PAX extended headers,
|
|
6
|
+
and GNU long names and base-256 numbers. The encoder writes USTAR, adding PAX
|
|
7
|
+
extended headers for fields that do not fit or for custom metadata. Neither touches
|
|
8
|
+
the file system or compresses.
|
|
9
|
+
|
|
10
|
+
Paths and link targets are returned as written in the archive, so they can be
|
|
11
|
+
absolute or contain ``..``, and a symlink entry can redirect a later entry outside
|
|
12
|
+
the target directory. Validate both before writing entries to disk.
|
|
13
|
+
"""
|
|
14
|
+
# pyright: reportPrivateUsage=false, reportUnknownVariableType=false, reportUnknownArgumentType=false, reportUnknownMemberType=false
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from collections.abc import Mapping
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
from typing import Any, Literal, Never, TypeIs
|
|
21
|
+
|
|
22
|
+
from pyfect._js import text_encode
|
|
23
|
+
from pyfect._primitives import _Succeed, _Suspend
|
|
24
|
+
from pyfect._public import publish
|
|
25
|
+
from pyfect.channel import Channel, from_transform
|
|
26
|
+
from pyfect.channel._base import AnyPull, end_with
|
|
27
|
+
from pyfect.encoding._archive import decode_channel
|
|
28
|
+
from pyfect.encoding.tar._decoder import Decoder
|
|
29
|
+
from pyfect.encoding.tar._encoder import SLAB_SIZE, Encoder
|
|
30
|
+
from pyfect.encoding.tar._header import EPOCH, TYPEFLAGS
|
|
31
|
+
from pyfect.encoding.tar._model import Entry, EntryType, FarDate, Header, TarError, TarErrorKind
|
|
32
|
+
from pyfect.exit import Success, void
|
|
33
|
+
from pyfect.exit import fail as fail_exit
|
|
34
|
+
from pyfect.pull import catch_done
|
|
35
|
+
from pyfect.scope import CloseableScope
|
|
36
|
+
from pyfect.stream import Stream, empty, succeed
|
|
37
|
+
from pyfect.stream._base import require_stream
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"Entry",
|
|
41
|
+
"EntryType",
|
|
42
|
+
"FarDate",
|
|
43
|
+
"Header",
|
|
44
|
+
"TarError",
|
|
45
|
+
"TarErrorKind",
|
|
46
|
+
"decode",
|
|
47
|
+
"directory",
|
|
48
|
+
"encode",
|
|
49
|
+
"file",
|
|
50
|
+
"file_stream",
|
|
51
|
+
"hardlink",
|
|
52
|
+
"is_tar_error",
|
|
53
|
+
"symlink",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def is_tar_error(value: object) -> TypeIs[TarError]:
|
|
58
|
+
return isinstance(value, TarError)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class _Content(Stream[bytes]):
|
|
62
|
+
"""A body held in memory, which ``encode`` writes without running it. It is the
|
|
63
|
+
``body`` that says so, so replacing an entry's body leaves that path."""
|
|
64
|
+
|
|
65
|
+
__slots__ = ("content",)
|
|
66
|
+
|
|
67
|
+
def __init__(self, content: bytes) -> None:
|
|
68
|
+
super().__init__(succeed(content)._channel)
|
|
69
|
+
self.content = content
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _header(
|
|
73
|
+
path: str,
|
|
74
|
+
kind: Literal["File", "Directory", "Symlink", "Hardlink"],
|
|
75
|
+
size: int,
|
|
76
|
+
link_target: str | None,
|
|
77
|
+
*,
|
|
78
|
+
mode: int | None,
|
|
79
|
+
uid: int | None,
|
|
80
|
+
gid: int | None,
|
|
81
|
+
uname: str | None,
|
|
82
|
+
gname: str | None,
|
|
83
|
+
mtime: datetime | FarDate | None,
|
|
84
|
+
pax: Mapping[str, str] | None,
|
|
85
|
+
) -> Header:
|
|
86
|
+
default_mode = 0o755 if kind == "Directory" else 0o777 if kind == "Symlink" else 0o644
|
|
87
|
+
return Header(
|
|
88
|
+
path=path,
|
|
89
|
+
type=kind,
|
|
90
|
+
typeflag=TYPEFLAGS[kind],
|
|
91
|
+
size=size,
|
|
92
|
+
mode=default_mode if mode is None else mode,
|
|
93
|
+
uid=0 if uid is None else uid,
|
|
94
|
+
gid=0 if gid is None else gid,
|
|
95
|
+
uname="" if uname is None else uname,
|
|
96
|
+
gname="" if gname is None else gname,
|
|
97
|
+
mtime=EPOCH if mtime is None else mtime,
|
|
98
|
+
link_target=link_target or None,
|
|
99
|
+
pax={} if pax is None else pax,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def file(
|
|
104
|
+
path: str,
|
|
105
|
+
content: str | bytes,
|
|
106
|
+
*,
|
|
107
|
+
mode: int | None = None,
|
|
108
|
+
uid: int | None = None,
|
|
109
|
+
gid: int | None = None,
|
|
110
|
+
uname: str | None = None,
|
|
111
|
+
gname: str | None = None,
|
|
112
|
+
mtime: datetime | FarDate | None = None,
|
|
113
|
+
pax: Mapping[str, str] | None = None,
|
|
114
|
+
) -> Entry:
|
|
115
|
+
"""A regular file from in-memory content; a ``str`` is encoded as UTF-8. Unset
|
|
116
|
+
fields default to mode ``0o644``, uid and gid 0, empty names, an mtime at the
|
|
117
|
+
epoch and no extra PAX records, which makes archives reproducible."""
|
|
118
|
+
data = text_encode(content) if isinstance(content, str) else content
|
|
119
|
+
header = _header(
|
|
120
|
+
path,
|
|
121
|
+
"File",
|
|
122
|
+
len(data),
|
|
123
|
+
None,
|
|
124
|
+
mode=mode,
|
|
125
|
+
uid=uid,
|
|
126
|
+
gid=gid,
|
|
127
|
+
uname=uname,
|
|
128
|
+
gname=gname,
|
|
129
|
+
mtime=mtime,
|
|
130
|
+
pax=pax,
|
|
131
|
+
)
|
|
132
|
+
return Entry(header, _Content(data) if data else empty)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def file_stream[E = Never, R = Never](
|
|
136
|
+
path: str,
|
|
137
|
+
size: int,
|
|
138
|
+
body: Stream[bytes, E, R],
|
|
139
|
+
*,
|
|
140
|
+
mode: int | None = None,
|
|
141
|
+
uid: int | None = None,
|
|
142
|
+
gid: int | None = None,
|
|
143
|
+
uname: str | None = None,
|
|
144
|
+
gname: str | None = None,
|
|
145
|
+
mtime: datetime | FarDate | None = None,
|
|
146
|
+
pax: Mapping[str, str] | None = None,
|
|
147
|
+
) -> Entry[E, R]:
|
|
148
|
+
"""A regular file streamed from ``body``, which runs when the entry is encoded.
|
|
149
|
+
``size`` is its exact byte length: ``encode`` fails with ``"SizeMismatch"``
|
|
150
|
+
otherwise."""
|
|
151
|
+
require_stream(body, "body")
|
|
152
|
+
header = _header(
|
|
153
|
+
path,
|
|
154
|
+
"File",
|
|
155
|
+
size,
|
|
156
|
+
None,
|
|
157
|
+
mode=mode,
|
|
158
|
+
uid=uid,
|
|
159
|
+
gid=gid,
|
|
160
|
+
uname=uname,
|
|
161
|
+
gname=gname,
|
|
162
|
+
mtime=mtime,
|
|
163
|
+
pax=pax,
|
|
164
|
+
)
|
|
165
|
+
return Entry(header, body)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def directory(
|
|
169
|
+
path: str,
|
|
170
|
+
*,
|
|
171
|
+
mode: int | None = None,
|
|
172
|
+
uid: int | None = None,
|
|
173
|
+
gid: int | None = None,
|
|
174
|
+
uname: str | None = None,
|
|
175
|
+
gname: str | None = None,
|
|
176
|
+
mtime: datetime | FarDate | None = None,
|
|
177
|
+
pax: Mapping[str, str] | None = None,
|
|
178
|
+
) -> Entry:
|
|
179
|
+
"""A directory (mode ``0o755`` by default). A trailing ``/`` is added to a
|
|
180
|
+
non-empty ``path``; an empty one stays empty, so that ``encode`` rejects it."""
|
|
181
|
+
named = path if path == "" or path.endswith("/") else f"{path}/"
|
|
182
|
+
header = _header(
|
|
183
|
+
named,
|
|
184
|
+
"Directory",
|
|
185
|
+
0,
|
|
186
|
+
None,
|
|
187
|
+
mode=mode,
|
|
188
|
+
uid=uid,
|
|
189
|
+
gid=gid,
|
|
190
|
+
uname=uname,
|
|
191
|
+
gname=gname,
|
|
192
|
+
mtime=mtime,
|
|
193
|
+
pax=pax,
|
|
194
|
+
)
|
|
195
|
+
return Entry(header, empty)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def symlink(
|
|
199
|
+
path: str,
|
|
200
|
+
target: str,
|
|
201
|
+
*,
|
|
202
|
+
mode: int | None = None,
|
|
203
|
+
uid: int | None = None,
|
|
204
|
+
gid: int | None = None,
|
|
205
|
+
uname: str | None = None,
|
|
206
|
+
gname: str | None = None,
|
|
207
|
+
mtime: datetime | FarDate | None = None,
|
|
208
|
+
pax: Mapping[str, str] | None = None,
|
|
209
|
+
) -> Entry:
|
|
210
|
+
"""A symbolic link to ``target`` (mode ``0o777`` by default)."""
|
|
211
|
+
header = _header(
|
|
212
|
+
path,
|
|
213
|
+
"Symlink",
|
|
214
|
+
0,
|
|
215
|
+
target,
|
|
216
|
+
mode=mode,
|
|
217
|
+
uid=uid,
|
|
218
|
+
gid=gid,
|
|
219
|
+
uname=uname,
|
|
220
|
+
gname=gname,
|
|
221
|
+
mtime=mtime,
|
|
222
|
+
pax=pax,
|
|
223
|
+
)
|
|
224
|
+
return Entry(header, empty)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def hardlink(
|
|
228
|
+
path: str,
|
|
229
|
+
target: str,
|
|
230
|
+
*,
|
|
231
|
+
mode: int | None = None,
|
|
232
|
+
uid: int | None = None,
|
|
233
|
+
gid: int | None = None,
|
|
234
|
+
uname: str | None = None,
|
|
235
|
+
gname: str | None = None,
|
|
236
|
+
mtime: datetime | FarDate | None = None,
|
|
237
|
+
pax: Mapping[str, str] | None = None,
|
|
238
|
+
) -> Entry:
|
|
239
|
+
"""A hard link to ``target``, the archive path of an entry written earlier."""
|
|
240
|
+
header = _header(
|
|
241
|
+
path,
|
|
242
|
+
"Hardlink",
|
|
243
|
+
0,
|
|
244
|
+
target,
|
|
245
|
+
mode=mode,
|
|
246
|
+
uid=uid,
|
|
247
|
+
gid=gid,
|
|
248
|
+
uname=uname,
|
|
249
|
+
gname=gname,
|
|
250
|
+
mtime=mtime,
|
|
251
|
+
pax=pax,
|
|
252
|
+
)
|
|
253
|
+
return Entry(header, empty)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def decode[IE = Never, D = None]() -> Channel[
|
|
257
|
+
tuple[Entry[TarError | IE], ...], TarError | IE, D, tuple[bytes, ...], IE, D
|
|
258
|
+
]:
|
|
259
|
+
"""A channel decoding archive bytes into entries, one entry per output chunk,
|
|
260
|
+
each body streaming ``header.size`` bytes straight from the input. Pulling the
|
|
261
|
+
next entry skips what is left of the current body. A PAX global header's records
|
|
262
|
+
apply to every entry after it, except ``path``, ``linkpath`` and ``size``.
|
|
263
|
+
|
|
264
|
+
Read each body before pulling the next entry and while the entry stream runs,
|
|
265
|
+
for example in ``map_effect``: a later read fails with ``"BodyConsumed"``, so
|
|
266
|
+
operators that pull ahead cannot sit between ``decode`` and the reads. A body
|
|
267
|
+
reads its bytes once: running it again continues where it stopped.
|
|
268
|
+
|
|
269
|
+
Directories, links, devices and FIFOs have no body and a ``size`` of 0. Names
|
|
270
|
+
that are not UTF-8 get replacement characters. PAX headers and GNU long names
|
|
271
|
+
past 1 MiB fail with ``"InvalidHeader"``, and GNU sparse entries with
|
|
272
|
+
``"Unsupported"``. The channel ends only when its input ends: input after the
|
|
273
|
+
end-of-archive marker is read and discarded, so that the upstream's done value
|
|
274
|
+
can be passed on.
|
|
275
|
+
|
|
276
|
+
An mtime ``datetime`` cannot hold (outside years 1 to 9999) fails with
|
|
277
|
+
``"InvalidHeader"``."""
|
|
278
|
+
return decode_channel(Decoder, Entry, TarError)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def encode[E = Never, R = Never, IE = Never, D = None]() -> Channel[ # noqa: PLR0915
|
|
282
|
+
tuple[bytes, ...], TarError | IE | E, D, tuple[Entry[E, R], ...], IE, D, R
|
|
283
|
+
]:
|
|
284
|
+
"""A channel encoding entries into archive bytes: a USTAR header per entry, a
|
|
285
|
+
PAX header only for what USTAR cannot hold (a long or non-ASCII path, link target
|
|
286
|
+
or name, a size of 8 GiB or more, a uid or gid above ``2097151``, an mtime outside
|
|
287
|
+
USTAR's range, or custom ``pax`` records), and two zero blocks at the end.
|
|
288
|
+
|
|
289
|
+
Output is written in chunks of about 64 KiB, and large body chunks pass through
|
|
290
|
+
uncopied, so bodies stream without being held in memory.
|
|
291
|
+
|
|
292
|
+
A body with any byte count but ``header.size`` fails with ``"SizeMismatch"``.
|
|
293
|
+
``header.pax`` holds custom metadata: the keys of header fields and
|
|
294
|
+
``GNU.sparse.*`` fail with ``"InvalidHeader"``, as does a ``File`` path ending in
|
|
295
|
+
``/``, a non-zero size on an entry other than ``File`` and ``Other``, or a device
|
|
296
|
+
number above ``2097151``. ``mtime`` is rounded down to whole seconds, and a naive
|
|
297
|
+
``datetime`` fails with ``"InvalidHeader"``. ``typeflag`` is only written for
|
|
298
|
+
``Other`` entries.
|
|
299
|
+
|
|
300
|
+
Entries that differ in their bodies' errors or services do not infer one ``E`` and
|
|
301
|
+
``R``: annotate the stream, as ``Stream[Entry[E1 | E2, R1 | R2]]``."""
|
|
302
|
+
|
|
303
|
+
def transform(upstream: AnyPull, scope: CloseableScope) -> AnyPull: # noqa: PLR0915
|
|
304
|
+
encoder = Encoder()
|
|
305
|
+
entries: tuple[Entry[Any, Any], ...] = ()
|
|
306
|
+
index = 0
|
|
307
|
+
done: tuple[Any] | None = None
|
|
308
|
+
body: AnyPull | None = None
|
|
309
|
+
body_scope: CloseableScope | None = None
|
|
310
|
+
|
|
311
|
+
def on_body(chunks: tuple[bytes, ...] | None) -> Any:
|
|
312
|
+
nonlocal body, body_scope
|
|
313
|
+
try:
|
|
314
|
+
if chunks is not None:
|
|
315
|
+
for chunk in chunks:
|
|
316
|
+
encoder.body(chunk)
|
|
317
|
+
return pull
|
|
318
|
+
except TarError as error:
|
|
319
|
+
return fail_exit(error)
|
|
320
|
+
body = None
|
|
321
|
+
assert body_scope is not None
|
|
322
|
+
closing = body_scope.close(void)
|
|
323
|
+
body_scope = None
|
|
324
|
+
return closing.flat_map(on_body_closed)
|
|
325
|
+
|
|
326
|
+
def on_body_closed(_: None) -> Any:
|
|
327
|
+
try:
|
|
328
|
+
encoder.end()
|
|
329
|
+
except TarError as error:
|
|
330
|
+
return fail_exit(error)
|
|
331
|
+
return pull
|
|
332
|
+
|
|
333
|
+
def begin(entry: Entry[Any, Any]) -> Any:
|
|
334
|
+
nonlocal body, body_scope
|
|
335
|
+
# Directories, links and in-memory files have nothing to stream
|
|
336
|
+
source = entry.body
|
|
337
|
+
data = (
|
|
338
|
+
b"" if source is empty else source.content if isinstance(source, _Content) else None
|
|
339
|
+
)
|
|
340
|
+
try:
|
|
341
|
+
encoder.begin(entry.header)
|
|
342
|
+
if data is not None:
|
|
343
|
+
encoder.body(data)
|
|
344
|
+
encoder.end()
|
|
345
|
+
except TarError as error:
|
|
346
|
+
return fail_exit(error)
|
|
347
|
+
if data is not None:
|
|
348
|
+
return pull
|
|
349
|
+
body_scope = scope.fork_unsafe()
|
|
350
|
+
|
|
351
|
+
def opened(opened_pull: AnyPull) -> Any:
|
|
352
|
+
nonlocal body
|
|
353
|
+
body = opened_pull
|
|
354
|
+
return pull
|
|
355
|
+
|
|
356
|
+
return source._channel.to_pull_scoped(body_scope).flat_map(opened)
|
|
357
|
+
|
|
358
|
+
def upstream_done(value: object) -> AnyPull:
|
|
359
|
+
nonlocal done
|
|
360
|
+
done = (value,)
|
|
361
|
+
encoder.finish()
|
|
362
|
+
return _Succeed(None)
|
|
363
|
+
|
|
364
|
+
def on_upstream(next_entries: tuple[Entry[Any, Any], ...] | None) -> Any:
|
|
365
|
+
nonlocal entries, index
|
|
366
|
+
if next_entries is not None:
|
|
367
|
+
entries = next_entries
|
|
368
|
+
index = 0
|
|
369
|
+
return pull
|
|
370
|
+
|
|
371
|
+
def step() -> Any:
|
|
372
|
+
nonlocal index
|
|
373
|
+
if encoder.buffered >= SLAB_SIZE:
|
|
374
|
+
return Success(encoder.flush())
|
|
375
|
+
if body is not None:
|
|
376
|
+
return catch_done(body, lambda _: _Succeed(None)).flat_map(on_body)
|
|
377
|
+
if index < len(entries):
|
|
378
|
+
entry = entries[index]
|
|
379
|
+
index += 1
|
|
380
|
+
return begin(entry)
|
|
381
|
+
if encoder.buffered > 0:
|
|
382
|
+
return Success(encoder.flush())
|
|
383
|
+
if done is not None:
|
|
384
|
+
return end_with(done[0])
|
|
385
|
+
return catch_done(upstream, upstream_done).flat_map(on_upstream)
|
|
386
|
+
|
|
387
|
+
pull: AnyPull = _Suspend(step)
|
|
388
|
+
return _Succeed(pull)
|
|
389
|
+
|
|
390
|
+
channel: Any = from_transform(
|
|
391
|
+
lambda upstream, scope: _Suspend(lambda: transform(upstream, scope))
|
|
392
|
+
)
|
|
393
|
+
return channel
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
publish(globals())
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Tar archive bytes to entries, pulled."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pyfect.encoding._archive import NEED_INPUT, ByteQueue, NeedInput
|
|
6
|
+
from pyfect.encoding.tar._header import (
|
|
7
|
+
BLOCK,
|
|
8
|
+
PERMISSION_BITS,
|
|
9
|
+
RawHeader,
|
|
10
|
+
apply_pax,
|
|
11
|
+
entry_type,
|
|
12
|
+
invalid_header,
|
|
13
|
+
is_header_only,
|
|
14
|
+
padding,
|
|
15
|
+
parse_header,
|
|
16
|
+
parse_pax,
|
|
17
|
+
read_string,
|
|
18
|
+
to_mtime,
|
|
19
|
+
unsupported,
|
|
20
|
+
)
|
|
21
|
+
from pyfect.encoding.tar._model import Header, TarError
|
|
22
|
+
|
|
23
|
+
_MAX_META_SIZE = 1024 * 1024 # PAX and GNU long name data
|
|
24
|
+
_ZERO_BLOCK = bytes(BLOCK)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Decoder:
|
|
28
|
+
"""``next`` skips unread body bytes; ``read`` consumes the current body. Both
|
|
29
|
+
keep their progress across ``NEED_INPUT``."""
|
|
30
|
+
|
|
31
|
+
def __init__(self) -> None:
|
|
32
|
+
self._queue = ByteQueue()
|
|
33
|
+
self._ended = False
|
|
34
|
+
self._finished = False
|
|
35
|
+
self._closed = False
|
|
36
|
+
# Incremented whenever the current body is invalidated
|
|
37
|
+
self.generation = 0
|
|
38
|
+
self._remaining = 0
|
|
39
|
+
self._pad = 0
|
|
40
|
+
# Bytes to discard before the next header
|
|
41
|
+
self._skip = 0
|
|
42
|
+
self._zero_block = False
|
|
43
|
+
# Archive offset of the last header block read, for error messages
|
|
44
|
+
self._at = 0
|
|
45
|
+
# An extended header whose data is still to be read
|
|
46
|
+
self._meta: RawHeader | None = None
|
|
47
|
+
self._pax: dict[str, str] | None = None
|
|
48
|
+
# Records of the PAX global headers read so far
|
|
49
|
+
self._global: dict[str, str] | None = None
|
|
50
|
+
self._long_name = ""
|
|
51
|
+
self._long_link = ""
|
|
52
|
+
|
|
53
|
+
def push(self, chunk: bytes) -> None:
|
|
54
|
+
if not self._finished:
|
|
55
|
+
self._queue.push(chunk)
|
|
56
|
+
|
|
57
|
+
def end(self) -> None:
|
|
58
|
+
self._ended = True
|
|
59
|
+
|
|
60
|
+
def close(self) -> None:
|
|
61
|
+
self._closed = True
|
|
62
|
+
self.generation += 1
|
|
63
|
+
self._queue.clear()
|
|
64
|
+
|
|
65
|
+
def next(self) -> Header | NeedInput | None:
|
|
66
|
+
"""The next entry header, or ``None`` once the archive and the input have
|
|
67
|
+
ended. Input after the end of the archive is discarded."""
|
|
68
|
+
try:
|
|
69
|
+
return self._advance()
|
|
70
|
+
except TarError as error:
|
|
71
|
+
# Header errors are raised where the archive offset is not known
|
|
72
|
+
if error.kind in ("InvalidHeader", "Unsupported"):
|
|
73
|
+
raise TarError(error.kind, f"{error.message} (header at byte {self._at})") from None
|
|
74
|
+
raise
|
|
75
|
+
|
|
76
|
+
def _advance(self) -> Header | NeedInput | None: # noqa: PLR0911, PLR0912
|
|
77
|
+
self.generation += 1
|
|
78
|
+
self._skip += self._remaining + self._pad
|
|
79
|
+
self._remaining = 0
|
|
80
|
+
self._pad = 0
|
|
81
|
+
queue = self._queue
|
|
82
|
+
while True:
|
|
83
|
+
if self._finished:
|
|
84
|
+
return self._finish()
|
|
85
|
+
while self._skip > 0:
|
|
86
|
+
if queue.length == 0:
|
|
87
|
+
return self._need_input()
|
|
88
|
+
self._skip -= len(queue.take_some(self._skip))
|
|
89
|
+
if self._meta is not None:
|
|
90
|
+
size = self._meta.size
|
|
91
|
+
typeflag = self._meta.typeflag
|
|
92
|
+
total = size + padding(size)
|
|
93
|
+
if queue.length < total:
|
|
94
|
+
return self._need_input()
|
|
95
|
+
data = queue.take(total)[:size]
|
|
96
|
+
self._meta = None
|
|
97
|
+
if typeflag == "x":
|
|
98
|
+
self._pax = parse_pax(data)
|
|
99
|
+
elif typeflag == "g":
|
|
100
|
+
self._add_global(parse_pax(data))
|
|
101
|
+
elif typeflag == "L":
|
|
102
|
+
self._long_name = read_string(data, 0, size)
|
|
103
|
+
else:
|
|
104
|
+
self._long_link = read_string(data, 0, size)
|
|
105
|
+
continue
|
|
106
|
+
if queue.length < BLOCK:
|
|
107
|
+
if queue.length == 0 and self._ended:
|
|
108
|
+
return self._finish()
|
|
109
|
+
return self._need_input()
|
|
110
|
+
self._at = queue.consumed
|
|
111
|
+
block = queue.take(BLOCK)
|
|
112
|
+
if block == _ZERO_BLOCK:
|
|
113
|
+
if self._zero_block:
|
|
114
|
+
return self._finish()
|
|
115
|
+
self._zero_block = True
|
|
116
|
+
continue
|
|
117
|
+
if self._zero_block:
|
|
118
|
+
raise invalid_header("non-zero block after an end-of-archive block")
|
|
119
|
+
raw = parse_header(block)
|
|
120
|
+
if raw.typeflag in ("x", "g", "L", "K"):
|
|
121
|
+
if raw.size > _MAX_META_SIZE:
|
|
122
|
+
raise invalid_header(f"extended header of {raw.size} bytes exceeds the limit")
|
|
123
|
+
self._meta = raw
|
|
124
|
+
continue
|
|
125
|
+
if raw.typeflag == "S":
|
|
126
|
+
raise unsupported("GNU sparse files are not supported")
|
|
127
|
+
header = self._to_header(raw)
|
|
128
|
+
self._remaining = header.size
|
|
129
|
+
self._pad = padding(header.size)
|
|
130
|
+
return header
|
|
131
|
+
|
|
132
|
+
def read(self, generation: int) -> tuple[bytes, ...] | NeedInput | None:
|
|
133
|
+
"""The buffered body bytes of the entry from ``generation``, or ``None``
|
|
134
|
+
once the whole body has been read."""
|
|
135
|
+
if generation != self.generation:
|
|
136
|
+
raise TarError(
|
|
137
|
+
"BodyConsumed",
|
|
138
|
+
"tar entry body read after the archive stream ended"
|
|
139
|
+
if self._closed
|
|
140
|
+
else "tar entry body read after moving to the next entry",
|
|
141
|
+
)
|
|
142
|
+
if self._remaining == 0:
|
|
143
|
+
return None
|
|
144
|
+
if self._queue.length == 0:
|
|
145
|
+
return self._need_input()
|
|
146
|
+
chunks: list[bytes] = []
|
|
147
|
+
while self._remaining > 0 and self._queue.length > 0:
|
|
148
|
+
chunk = self._queue.take_some(self._remaining)
|
|
149
|
+
self._remaining -= len(chunk)
|
|
150
|
+
chunks.append(chunk)
|
|
151
|
+
return tuple(chunks)
|
|
152
|
+
|
|
153
|
+
def _need_input(self) -> NeedInput:
|
|
154
|
+
if self._ended:
|
|
155
|
+
raise _truncated(self._queue.consumed + self._queue.length)
|
|
156
|
+
return NEED_INPUT
|
|
157
|
+
|
|
158
|
+
def _finish(self) -> NeedInput | None:
|
|
159
|
+
# An extended header must be followed by the entry it describes
|
|
160
|
+
if not self._finished and (
|
|
161
|
+
self._pax is not None or self._long_name != "" or self._long_link != ""
|
|
162
|
+
):
|
|
163
|
+
raise _truncated(self._queue.consumed)
|
|
164
|
+
self._finished = True
|
|
165
|
+
self._queue.clear()
|
|
166
|
+
return None if self._ended else NEED_INPUT
|
|
167
|
+
|
|
168
|
+
def _add_global(self, records: dict[str, str]) -> None:
|
|
169
|
+
"""Global records apply to every entry that follows, except the ones that
|
|
170
|
+
only describe a single entry, and an empty value removes a record."""
|
|
171
|
+
for key, value in records.items():
|
|
172
|
+
if key in ("path", "linkpath", "size"):
|
|
173
|
+
continue
|
|
174
|
+
if value != "":
|
|
175
|
+
if self._global is None:
|
|
176
|
+
self._global = {}
|
|
177
|
+
self._global[key] = value
|
|
178
|
+
elif self._global is not None:
|
|
179
|
+
self._global.pop(key, None)
|
|
180
|
+
if not self._global:
|
|
181
|
+
self._global = None
|
|
182
|
+
|
|
183
|
+
def _to_header(self, raw: RawHeader) -> Header:
|
|
184
|
+
# The records of the entry's own extended header override global ones
|
|
185
|
+
if self._global is None:
|
|
186
|
+
records = self._pax
|
|
187
|
+
else:
|
|
188
|
+
records = self._global if self._pax is None else {**self._global, **self._pax}
|
|
189
|
+
rest = {} if records is None else apply_pax(raw, records)
|
|
190
|
+
# Empty GNU long names are ignored, as in Go
|
|
191
|
+
if self._long_name != "":
|
|
192
|
+
raw.path = self._long_name
|
|
193
|
+
if self._long_link != "":
|
|
194
|
+
raw.link_target = self._long_link
|
|
195
|
+
self._pax = None
|
|
196
|
+
self._long_name = ""
|
|
197
|
+
self._long_link = ""
|
|
198
|
+
mtime = to_mtime(raw.mtime_millis)
|
|
199
|
+
kind = entry_type(raw.typeflag, raw.path)
|
|
200
|
+
device = kind in ("CharDevice", "BlockDevice")
|
|
201
|
+
return Header(
|
|
202
|
+
path=raw.path,
|
|
203
|
+
type=kind,
|
|
204
|
+
typeflag=raw.typeflag,
|
|
205
|
+
size=0 if is_header_only(kind) else raw.size,
|
|
206
|
+
mode=raw.mode & PERMISSION_BITS,
|
|
207
|
+
uid=raw.uid,
|
|
208
|
+
gid=raw.gid,
|
|
209
|
+
uname=raw.uname,
|
|
210
|
+
gname=raw.gname,
|
|
211
|
+
mtime=mtime,
|
|
212
|
+
link_target=None if raw.link_target == "" else raw.link_target,
|
|
213
|
+
dev_major=raw.dev_major if device else None,
|
|
214
|
+
dev_minor=raw.dev_minor if device else None,
|
|
215
|
+
pax=rest,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _truncated(at: int) -> TarError:
|
|
220
|
+
return TarError("Truncated", f"unexpected end of tar archive at byte {at}")
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Entries to tar archive bytes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pyfect._json_text import quote as quoted
|
|
6
|
+
from pyfect.encoding.tar._header import BLOCK, encode_header, padding
|
|
7
|
+
from pyfect.encoding.tar._model import Header, TarError
|
|
8
|
+
|
|
9
|
+
SLAB_SIZE = 64 * 1024
|
|
10
|
+
|
|
11
|
+
_COPY_LIMIT = 16 * 1024
|
|
12
|
+
"""Bodies below this size are copied next to their header instead of being handed
|
|
13
|
+
out as chunks of their own."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _size_mismatch(header: Header, written: int) -> TarError:
|
|
17
|
+
return TarError(
|
|
18
|
+
"SizeMismatch",
|
|
19
|
+
f"body of {quoted(header.path)} has {'at least' if written > header.size else 'only'} "
|
|
20
|
+
f"{written} bytes, but its size is {header.size}",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Encoder:
|
|
25
|
+
"""Packs headers, padding and small body chunks into runs of up to 64 KiB,
|
|
26
|
+
and passes large chunks through without copying."""
|
|
27
|
+
|
|
28
|
+
__slots__ = ("_chunks", "_header", "_run", "_written", "buffered")
|
|
29
|
+
|
|
30
|
+
def __init__(self) -> None:
|
|
31
|
+
self._run = bytearray()
|
|
32
|
+
self._chunks: list[bytes] = []
|
|
33
|
+
self.buffered = 0
|
|
34
|
+
# The entry whose body is being written, and the body bytes written so far
|
|
35
|
+
self._header: Header | None = None
|
|
36
|
+
self._written = 0
|
|
37
|
+
|
|
38
|
+
def begin(self, header: Header) -> None:
|
|
39
|
+
self._add(encode_header(header))
|
|
40
|
+
self._header = header
|
|
41
|
+
self._written = 0
|
|
42
|
+
|
|
43
|
+
def body(self, chunk: bytes) -> None:
|
|
44
|
+
header = self._header
|
|
45
|
+
assert header is not None
|
|
46
|
+
self._written += len(chunk)
|
|
47
|
+
if self._written > header.size:
|
|
48
|
+
raise _size_mismatch(header, self._written)
|
|
49
|
+
if len(chunk) < _COPY_LIMIT:
|
|
50
|
+
self._add(chunk)
|
|
51
|
+
return
|
|
52
|
+
self._cut()
|
|
53
|
+
self._chunks.append(chunk)
|
|
54
|
+
self.buffered += len(chunk)
|
|
55
|
+
|
|
56
|
+
def end(self) -> None:
|
|
57
|
+
header = self._header
|
|
58
|
+
assert header is not None
|
|
59
|
+
self._header = None
|
|
60
|
+
if self._written != header.size:
|
|
61
|
+
raise _size_mismatch(header, self._written)
|
|
62
|
+
self._add(bytes(padding(header.size)))
|
|
63
|
+
|
|
64
|
+
def finish(self) -> None:
|
|
65
|
+
self._add(bytes(BLOCK * 2))
|
|
66
|
+
|
|
67
|
+
def flush(self) -> tuple[bytes, ...]:
|
|
68
|
+
self._cut()
|
|
69
|
+
chunks = tuple(self._chunks)
|
|
70
|
+
self._chunks = []
|
|
71
|
+
self.buffered = 0
|
|
72
|
+
return chunks
|
|
73
|
+
|
|
74
|
+
def _cut(self) -> None:
|
|
75
|
+
if self._run:
|
|
76
|
+
self._chunks.append(bytes(self._run))
|
|
77
|
+
self._run.clear()
|
|
78
|
+
|
|
79
|
+
def _add(self, data: bytes) -> None:
|
|
80
|
+
if len(self._run) + len(data) > SLAB_SIZE:
|
|
81
|
+
self._cut()
|
|
82
|
+
self._run += data
|
|
83
|
+
self.buffered += len(data)
|