withruntime 0.9.0__tar.gz → 0.10.0__tar.gz
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.
- {withruntime-0.9.0 → withruntime-0.10.0}/PKG-INFO +15 -6
- {withruntime-0.9.0 → withruntime-0.10.0}/README.md +14 -5
- {withruntime-0.9.0 → withruntime-0.10.0}/pyproject.toml +4 -1
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_blaxel.py +427 -6
- withruntime-0.10.0/tests/test_blaxel_wait_deadline.py +57 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_client.py +3 -1
- withruntime-0.10.0/tests/test_compat_modal_deadline_errors.py +73 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_new_providers.py +4 -4
- withruntime-0.10.0/tests/test_compat_prime_process_lifecycle.py +106 -0
- withruntime-0.10.0/tests/test_compat_prime_validation.py +274 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_python_interpreter_deadline.py +4 -1
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_python_request_scope.py +5 -1
- withruntime-0.10.0/tests/test_customer_usage.py +127 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_daytona.py +1 -1
- withruntime-0.10.0/tests/test_daytona_create_cleanup.py +75 -0
- withruntime-0.10.0/tests/test_daytona_download_labels.py +153 -0
- withruntime-0.10.0/tests/test_e2b_fork_validation.py +78 -0
- withruntime-0.10.0/tests/test_exec_stream_names.py +51 -0
- withruntime-0.10.0/tests/test_file_archives.py +174 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_harbor.py +56 -3
- withruntime-0.10.0/tests/test_package_typing.py +142 -0
- withruntime-0.10.0/tests/test_process_write_backpressure.py +174 -0
- withruntime-0.10.0/tests/test_product_aliases.py +87 -0
- withruntime-0.10.0/tests/test_runloop_stream_cleanup.py +167 -0
- withruntime-0.10.0/tests/test_saved_connection.py +37 -0
- withruntime-0.10.0/tests/test_snapshot_recovery.py +128 -0
- withruntime-0.10.0/tests/test_transport_body_recovery.py +229 -0
- withruntime-0.10.0/tests/test_transport_connection_limits.py +256 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_unpack.py +64 -0
- withruntime-0.10.0/tests/test_vercel_create_validation.py +158 -0
- withruntime-0.10.0/tests/test_vercel_disk_snapshot.py +391 -0
- withruntime-0.10.0/tests/test_volume_operations.py +52 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/__init__.py +5 -1
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_client.py +159 -56
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/previews.py +14 -4
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/volumes.py +24 -2
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_connection.py +5 -2
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_http.py +109 -75
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_proxy.py +17 -3
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_client.py +159 -56
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/previews.py +14 -4
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/volumes.py +24 -2
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_unpack.py +67 -6
- withruntime-0.10.0/withruntime/_version.py +1 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/__init__.py +2 -2
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/_async_io.py +6 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/_async_sandbox.py +71 -30
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/_core.py +49 -3
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/_sync_io.py +9 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/_sync_sandbox.py +71 -30
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/models/__init__.py +1 -1
- withruntime-0.10.0/withruntime/blaxel/core/client/models/pagination_meta.py +2 -0
- withruntime-0.10.0/withruntime/blaxel/core/client/pagination.py +2 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/daytona/_async_daytona.py +39 -20
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/daytona/_sync_daytona.py +39 -20
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/_async_sandbox.py +9 -5
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/_sync_sandbox.py +9 -5
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/harbor.py +5 -30
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/modal/__init__.py +3 -3
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/modal/_async.py +2 -3
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/prime/__init__.py +7 -8
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/prime/_async.py +29 -6
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/prime/_sync.py +29 -6
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/prime/exceptions.py +5 -4
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/prime/models.py +81 -7
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/prime/process.py +10 -1
- withruntime-0.10.0/withruntime/py.typed +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/runloop/_async.py +20 -5
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/runloop/_sync.py +20 -5
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/vercel/_async_sandbox.py +94 -15
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/vercel/_core.py +5 -2
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/vercel/_sync_sandbox.py +94 -15
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime.egg-info/PKG-INFO +15 -6
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime.egg-info/SOURCES.txt +24 -0
- withruntime-0.9.0/withruntime/_version.py +0 -1
- {withruntime-0.9.0 → withruntime-0.10.0}/LICENSE +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/setup.cfg +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_account_errors.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_async_stream_cleanup.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_audit.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_billing.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_capacity_wait.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_live.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_python_disk_snapshots.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_python_e2b_deadline.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_python_e2b_pty.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_python_e2b_stream.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_python_e2b_watch.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_python_modal.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_python_official.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_python_pins.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_python_processes.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_python_runloop.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_python_shell.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_python_snapshot_capture.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_python_sprites.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_compat_router.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_deepagents.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_e2b_code_interpreter.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_e2b_sandbox.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_e2b_watch.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_embed_and_webhook_watch.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_eval_sandbox.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_exec_stream.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_files_download.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_files_mode.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_files_upload_parallel.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_http_tls_close.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_identity_token.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_images_v2.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_images_volumes_interpreter.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_inspect_ai.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_interface_contract.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_jobs.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_late_answer.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_lifecycle_settings.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_limits.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_mounts.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_network_funding.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_network_products.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_observability.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_openai_agents.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_previews_network_desktop.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_process_deadline.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_process_input.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_process_output.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_proxy.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_referrals.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_sandbox_origin.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_sandbox_sessions.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_secrets.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_snapshot_after_fork.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_sso.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_start_report_kept.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_stream_exit_close.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_switch_image.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_switching.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_tailscale.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_tools.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_vercel.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_volume_backups.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_wake_awake.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_wallet.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/tests/test_watch.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_api_defaults.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/audit.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/billing.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/desktop.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/images.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/interpreter.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/jobs.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/limits.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/mcp.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/mounts.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/network.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/network_products.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/observability.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/referrals.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/secrets.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/sso.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/switching.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/tailscale.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_async_products/watch.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_clock.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_compat.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_compat_lifecycle.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_compat_shell.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_errors.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_eval_sandbox.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_request_scope.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/audit.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/billing.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/desktop.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/images.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/interpreter.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/jobs.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/limits.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/mcp.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/mounts.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/network.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/network_products.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/observability.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/referrals.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/secrets.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/sso.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/switching.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/tailscale.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_sync_products/watch.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_tunnel.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/_ws.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/agents.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/api/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/api/applications/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/api/applications/list_application_revisions.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/api/compute/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/api/compute/get_sandbox.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/api/compute/update_sandbox.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/api/functions/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/api/functions/create_function.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/api/images.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/api/workspaces.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/client.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/models/create_job_execution_request.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/models/create_job_execution_request_env.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/models/env.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/models/function.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/models/function_runtime.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/models/function_spec.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/models/metadata.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/models/workspace.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/client/types.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/common.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/drive.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/jobs.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/sandbox/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/core/sandbox/types.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/crewai.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/googleadk.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/langgraph.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/livekit.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/llamaindex.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/openai.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/pydantic.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/blaxel/telemetry.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/daytona/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/daytona/_async_io.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/daytona/_core.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/daytona/_sync_io.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/deepagents.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/_async_io.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/_core.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/_sync_io.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/code_interpreter/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/code_interpreter/_async_ci.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/code_interpreter/_models.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/code_interpreter/_sync_ci.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/code_interpreter/charts.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox/commands/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox/commands/command_handle.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox/filesystem/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox/filesystem/filesystem.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox_async/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox_async/commands/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox_async/commands/command.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox_async/commands/command_handle.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox_async/commands/pty.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox_async/filesystem/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox_async/filesystem/filesystem.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox_sync/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox_sync/commands/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox_sync/commands/command.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox_sync/commands/command_handle.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox_sync/commands/pty.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox_sync/filesystem/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/e2b/sandbox_sync/filesystem/filesystem.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/inspect_ai.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/modal/_dual.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/modal/_fs_async.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/modal/_fs_common.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/modal/_fs_sync.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/modal/_lookup.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/modal/_snapshot.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/modal/exception.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/modal/sandbox_fs.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/modal/types.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/openai_agents.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/prime/core/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/prime/core/client.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/prime/sandbox.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/runloop/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/runloop/_exceptions.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/runloop/_snapshots.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/runloop/lib/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/runloop/lib/polling.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/runloop/sdk/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/sprites/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/sprites/_capture.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/sprites/_capture_async.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/sprites/_metadata.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/sprites/_stream.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/sprites/_stream_async.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/sprites/async_client.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/sprites/async_exec.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/sprites/client.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/sprites/exceptions.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/sprites/exec.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/sprites/filesystem.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/sprites/sprite.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/sprites/types.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/tools.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/vercel/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/vercel/_async_io.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/vercel/_sync_io.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/vercel/api.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/vercel/sandbox/__init__.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/vercel/sandbox/sync.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/wallet.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime/webhooks.py +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime.egg-info/dependency_links.txt +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime.egg-info/entry_points.txt +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime.egg-info/requires.txt +0 -0
- {withruntime-0.9.0 → withruntime-0.10.0}/withruntime.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: withruntime
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: Runtime Cloud: one client (sync and async) for every Runtime product. Sandboxes first.
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://withruntime.com
|
|
@@ -44,7 +44,8 @@ command-line argument.
|
|
|
44
44
|
from withruntime import Sandbox
|
|
45
45
|
|
|
46
46
|
with Sandbox.create() as sbx:
|
|
47
|
-
|
|
47
|
+
sbx.files.write("/workspace/invoice.py", "print(sum([125, 250, 375]))\n")
|
|
48
|
+
result = sbx.exec("python3 /workspace/invoice.py")
|
|
48
49
|
print(result.exit_code, result.stdout)
|
|
49
50
|
```
|
|
50
51
|
|
|
@@ -53,6 +54,8 @@ leaving the `with` block stops it. With no arguments you get the free trial
|
|
|
53
54
|
while it lasts: 100 free hours, no card, up to eight sandboxes running at once.
|
|
54
55
|
The current default image includes NumPy, pandas and matplotlib; see the
|
|
55
56
|
[sandbox environment](https://withruntime.com/docs/sandbox-environment).
|
|
57
|
+
Omitting `funding` can use prepaid credit after the trial is exhausted. Use
|
|
58
|
+
`Sandbox.create(funding="trial")` for free use only; it never falls back to paid.
|
|
56
59
|
`AsyncRuntime` is the same client for asyncio, method for method:
|
|
57
60
|
|
|
58
61
|
```python
|
|
@@ -83,14 +86,20 @@ also wakes by itself on the next call, and `Sandbox.get_or_create(name)` returns
|
|
|
83
86
|
the sandbox with that name or creates it. A sandbox pauses itself after 60 seconds
|
|
84
87
|
with nothing happening in it, keeping its memory and processes, so an
|
|
85
88
|
idle sandbox costs no compute; `idle_pause_seconds` sets 10 to 86,400, or 0 for
|
|
86
|
-
never.
|
|
89
|
+
never. `runtime.sandbox`, `snapshot`, `image`, `volume`, `job`, `domain`,
|
|
90
|
+
`port` and `address` name the products as the CLI does. Their existing plural
|
|
91
|
+
names remain aliases of the same product clients. The client has `sandboxes`, `images`, `volumes`, `snapshots`, `jobs`,
|
|
87
92
|
`secrets`, `limits`, `feedback` and `support`; `webhooks`, `events`, `otel` and
|
|
88
93
|
`audit` to watch the account; `domains`, `ports`, `addresses`, `tunnel` and
|
|
89
94
|
`network` to connect sandboxes to your own world (paid accounts); and `mcp`,
|
|
90
95
|
`sso`, `billing`, `referrals` and `switching`. `runtime.limits.get()` (0.3.1 and later) says whether the key is read-only and
|
|
91
96
|
what its agent may still spend today. Every write carries an
|
|
92
97
|
idempotency key, so retries never do anything twice; errors are typed and carry
|
|
93
|
-
`code`, `hint` and `request_id`.
|
|
98
|
+
`code`, `hint` and `request_id`. `RuntimeAPIError` and
|
|
99
|
+
`RuntimeConnectionError` name the SDK errors without shadowing Python's built-in
|
|
100
|
+
errors; the previous `RuntimeError` and `ConnectionError` exports remain aliases.
|
|
101
|
+
`runtime.usage_requests("7d")` reads the account's API call counts and error rates;
|
|
102
|
+
counts are exact decimal strings, and an empty window has `errorPercent: None`.
|
|
94
103
|
|
|
95
104
|
## Behind a proxy
|
|
96
105
|
|
|
@@ -109,7 +118,7 @@ proxy http://proxy.internal:3128 (HTTPS_PROXY).`
|
|
|
109
118
|
|
|
110
119
|
## Code written for E2B
|
|
111
120
|
|
|
112
|
-
`withruntime.e2b`
|
|
121
|
+
`withruntime.e2b` translates supported E2B Python sandbox calls. Change
|
|
113
122
|
the import and set `RUNTIME_API_KEY`:
|
|
114
123
|
|
|
115
124
|
```python no-run
|
|
@@ -147,7 +156,7 @@ JavaScript package list every mapping and gap.
|
|
|
147
156
|
|
|
148
157
|
## Code written for Blaxel
|
|
149
158
|
|
|
150
|
-
`withruntime.blaxel`
|
|
159
|
+
`withruntime.blaxel` translates supported Blaxel Python sandbox calls (`blaxel`
|
|
151
160
|
0.4.11) on Runtime. Change the import and set `RUNTIME_API_KEY`, or run
|
|
152
161
|
`npx withruntime login` once:
|
|
153
162
|
|
|
@@ -19,7 +19,8 @@ command-line argument.
|
|
|
19
19
|
from withruntime import Sandbox
|
|
20
20
|
|
|
21
21
|
with Sandbox.create() as sbx:
|
|
22
|
-
|
|
22
|
+
sbx.files.write("/workspace/invoice.py", "print(sum([125, 250, 375]))\n")
|
|
23
|
+
result = sbx.exec("python3 /workspace/invoice.py")
|
|
23
24
|
print(result.exit_code, result.stdout)
|
|
24
25
|
```
|
|
25
26
|
|
|
@@ -28,6 +29,8 @@ leaving the `with` block stops it. With no arguments you get the free trial
|
|
|
28
29
|
while it lasts: 100 free hours, no card, up to eight sandboxes running at once.
|
|
29
30
|
The current default image includes NumPy, pandas and matplotlib; see the
|
|
30
31
|
[sandbox environment](https://withruntime.com/docs/sandbox-environment).
|
|
32
|
+
Omitting `funding` can use prepaid credit after the trial is exhausted. Use
|
|
33
|
+
`Sandbox.create(funding="trial")` for free use only; it never falls back to paid.
|
|
31
34
|
`AsyncRuntime` is the same client for asyncio, method for method:
|
|
32
35
|
|
|
33
36
|
```python
|
|
@@ -58,14 +61,20 @@ also wakes by itself on the next call, and `Sandbox.get_or_create(name)` returns
|
|
|
58
61
|
the sandbox with that name or creates it. A sandbox pauses itself after 60 seconds
|
|
59
62
|
with nothing happening in it, keeping its memory and processes, so an
|
|
60
63
|
idle sandbox costs no compute; `idle_pause_seconds` sets 10 to 86,400, or 0 for
|
|
61
|
-
never.
|
|
64
|
+
never. `runtime.sandbox`, `snapshot`, `image`, `volume`, `job`, `domain`,
|
|
65
|
+
`port` and `address` name the products as the CLI does. Their existing plural
|
|
66
|
+
names remain aliases of the same product clients. The client has `sandboxes`, `images`, `volumes`, `snapshots`, `jobs`,
|
|
62
67
|
`secrets`, `limits`, `feedback` and `support`; `webhooks`, `events`, `otel` and
|
|
63
68
|
`audit` to watch the account; `domains`, `ports`, `addresses`, `tunnel` and
|
|
64
69
|
`network` to connect sandboxes to your own world (paid accounts); and `mcp`,
|
|
65
70
|
`sso`, `billing`, `referrals` and `switching`. `runtime.limits.get()` (0.3.1 and later) says whether the key is read-only and
|
|
66
71
|
what its agent may still spend today. Every write carries an
|
|
67
72
|
idempotency key, so retries never do anything twice; errors are typed and carry
|
|
68
|
-
`code`, `hint` and `request_id`.
|
|
73
|
+
`code`, `hint` and `request_id`. `RuntimeAPIError` and
|
|
74
|
+
`RuntimeConnectionError` name the SDK errors without shadowing Python's built-in
|
|
75
|
+
errors; the previous `RuntimeError` and `ConnectionError` exports remain aliases.
|
|
76
|
+
`runtime.usage_requests("7d")` reads the account's API call counts and error rates;
|
|
77
|
+
counts are exact decimal strings, and an empty window has `errorPercent: None`.
|
|
69
78
|
|
|
70
79
|
## Behind a proxy
|
|
71
80
|
|
|
@@ -84,7 +93,7 @@ proxy http://proxy.internal:3128 (HTTPS_PROXY).`
|
|
|
84
93
|
|
|
85
94
|
## Code written for E2B
|
|
86
95
|
|
|
87
|
-
`withruntime.e2b`
|
|
96
|
+
`withruntime.e2b` translates supported E2B Python sandbox calls. Change
|
|
88
97
|
the import and set `RUNTIME_API_KEY`:
|
|
89
98
|
|
|
90
99
|
```python no-run
|
|
@@ -122,7 +131,7 @@ JavaScript package list every mapping and gap.
|
|
|
122
131
|
|
|
123
132
|
## Code written for Blaxel
|
|
124
133
|
|
|
125
|
-
`withruntime.blaxel`
|
|
134
|
+
`withruntime.blaxel` translates supported Blaxel Python sandbox calls (`blaxel`
|
|
126
135
|
0.4.11) on Runtime. Change the import and set `RUNTIME_API_KEY`, or run
|
|
127
136
|
`npx withruntime login` once:
|
|
128
137
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "withruntime"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.10.0"
|
|
8
8
|
description = "Runtime Cloud: one client (sync and async) for every Runtime product. Sandboxes first."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
@@ -36,3 +36,6 @@ withruntime = "withruntime.inspect_ai"
|
|
|
36
36
|
[tool.setuptools.packages.find]
|
|
37
37
|
where = ["."]
|
|
38
38
|
include = ["withruntime*"]
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.package-data]
|
|
41
|
+
withruntime = ["py.typed"]
|
|
@@ -255,9 +255,9 @@ class Create(Base):
|
|
|
255
255
|
"archive it (sandbox.archive()), then try again. Moving to paid credit is the account "
|
|
256
256
|
"owner's decision.",
|
|
257
257
|
"public_preview_not_allowed": 'On the trial, share the port privately: sandbox.previews.create({"metadata": '
|
|
258
|
-
'{"name": ...}, "spec": {"port": ..., "public": False}}) and a
|
|
259
|
-
"
|
|
260
|
-
"which is the account owner's decision.",
|
|
258
|
+
'{"name": ...}, "spec": {"port": ..., "public": False}}) and a Runtime '
|
|
259
|
+
"duration-based token from sandbox.withruntime.previews.get(port, ttl_seconds=seconds). "
|
|
260
|
+
"A public preview needs a paid sandbox, which is the account owner's decision.",
|
|
261
261
|
"busy": "Try again in a moment.", "guest_busy": "Try again in a moment.",
|
|
262
262
|
"rate_limited": "Try again in a moment.",
|
|
263
263
|
"unauthorized": "Set RUNTIME_API_KEY to a Runtime key (https://withruntime.com/account/keys), or run "
|
|
@@ -710,6 +710,24 @@ class Processes(Base):
|
|
|
710
710
|
|
|
711
711
|
|
|
712
712
|
class Files(Base):
|
|
713
|
+
def test_failed_privileged_write_removes_its_staging_bytes(self):
|
|
714
|
+
box = self.create()
|
|
715
|
+
error = ResponseError('move failed', 422, 'permission_denied')
|
|
716
|
+
undo = self.refuse(box, 'write', {'/root/private'})
|
|
717
|
+
def fail(*args):
|
|
718
|
+
raise error
|
|
719
|
+
box.fs._sudo = fail
|
|
720
|
+
try:
|
|
721
|
+
with self.assertRaises(ResponseError) as caught:
|
|
722
|
+
box.fs.write('/root/private', 'private bytes')
|
|
723
|
+
finally:
|
|
724
|
+
undo()
|
|
725
|
+
self.assertIs(caught.exception, error)
|
|
726
|
+
self.assertEqual(self.runtime(box).file_map, {})
|
|
727
|
+
removed = self.world.called('files.remove')
|
|
728
|
+
self.assertEqual(len(removed), 1)
|
|
729
|
+
self.assertTrue(removed[0][0].startswith('/workspace/.runtime-blaxel-'))
|
|
730
|
+
|
|
713
731
|
def test_paths_and_shapes(self):
|
|
714
732
|
box = self.create()
|
|
715
733
|
written = box.fs.write("/blaxel/app/config.json", "{}")
|
|
@@ -867,6 +885,20 @@ class Files(Base):
|
|
|
867
885
|
|
|
868
886
|
|
|
869
887
|
class Previews(Base):
|
|
888
|
+
def test_invalid_token_expirations_refuse_before_lookup_or_native_requests(self):
|
|
889
|
+
box = SyncSandboxInstance({"metadata": {"name": "unresolved-source"}})
|
|
890
|
+
preview = _sync_sandbox.SandboxPreview(bl.Preview.from_dict({
|
|
891
|
+
"metadata": {"name": "app"}, "spec": {"port": 3000, "public": False}}), box)
|
|
892
|
+
now = datetime.now(timezone.utc)
|
|
893
|
+
for seconds in (-1, 0, 5, 59.999, 8 * 86400, 40 * 86400):
|
|
894
|
+
with self.subTest(seconds=seconds), self.assertRaises(ValueError):
|
|
895
|
+
preview.tokens.create(now + timedelta(seconds=seconds))
|
|
896
|
+
self.assertEqual(self.world.calls, [])
|
|
897
|
+
for invalid in (None, "not-a-date", object(), datetime.now() + timedelta(minutes=10)):
|
|
898
|
+
with self.assertRaises(ValueError):
|
|
899
|
+
preview.tokens.create(invalid)
|
|
900
|
+
self.assertEqual(self.world.calls, [])
|
|
901
|
+
|
|
870
902
|
def test_create_get_tokens_delete(self):
|
|
871
903
|
box = self.create({"name": "web"})
|
|
872
904
|
preview = box.previews.create({"metadata": {"name": "app"}, "spec": {"port": 3000, "public": False}})
|
|
@@ -876,9 +908,15 @@ class Previews(Base):
|
|
|
876
908
|
other = SyncSandboxInstance.get("web")
|
|
877
909
|
self.assertEqual(other.previews.get("app").spec.port, 3000)
|
|
878
910
|
self.assertEqual([one.name for one in other.previews.list()], ["app"])
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
self.
|
|
911
|
+
deadline = datetime.now(timezone(timedelta(hours=5, minutes=30))) + timedelta(minutes=10, microseconds=123)
|
|
912
|
+
private = preview.tokens.create(deadline)
|
|
913
|
+
self.assertEqual(private.value, "tok-1")
|
|
914
|
+
self.assertLessEqual(private.expires_at, deadline)
|
|
915
|
+
self.assertEqual(self.world.called("previews.get")[-1],
|
|
916
|
+
(3000, None, deadline.astimezone(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")))
|
|
917
|
+
token = box.withruntime.previews.get(3000, ttl_seconds=600)
|
|
918
|
+
self.assertEqual(token["token"], "tok-1")
|
|
919
|
+
self.assertEqual(self.world.called("previews.get")[-1][1], 600)
|
|
882
920
|
with self.assertRaises(NotSupportedError):
|
|
883
921
|
preview.tokens.list()
|
|
884
922
|
with self.assertRaises(NotSupportedError) as caught:
|
|
@@ -893,6 +931,37 @@ class Previews(Base):
|
|
|
893
931
|
box.previews.get("app")
|
|
894
932
|
self.assertEqual(caught.exception.status_code, 404)
|
|
895
933
|
|
|
934
|
+
def test_exact_deadline_rejects_bad_responses_and_never_retries_an_old_server(self):
|
|
935
|
+
box = self.create({"name": "web"})
|
|
936
|
+
preview = box.previews.create({"metadata": {"name": "app"}, "spec": {"port": 3000}})
|
|
937
|
+
native = box.withruntime.previews
|
|
938
|
+
deadline = datetime.now(timezone.utc) + timedelta(minutes=10)
|
|
939
|
+
replies = ({"token": "must-not-escape"},
|
|
940
|
+
{"token": "must-not-escape", "tokenExpiresAt": "not-a-date"},
|
|
941
|
+
{"token": "must-not-escape", "tokenExpiresAt": (deadline + timedelta(seconds=1)).isoformat()},
|
|
942
|
+
{"token": "must-not-escape", "tokenExpiresAt": deadline.replace(tzinfo=None).isoformat()},
|
|
943
|
+
{"token": None, "tokenExpiresAt": None})
|
|
944
|
+
for reply in replies:
|
|
945
|
+
calls = []
|
|
946
|
+
def get(port, **options):
|
|
947
|
+
calls.append((port, options))
|
|
948
|
+
return reply
|
|
949
|
+
native.get = get
|
|
950
|
+
with self.subTest(reply=reply), self.assertRaises(SandboxAPIError):
|
|
951
|
+
preview.tokens.create(deadline)
|
|
952
|
+
self.assertEqual(len(calls), 1)
|
|
953
|
+
self.assertEqual(set(calls[0][1]), {"expires_at"})
|
|
954
|
+
calls = []
|
|
955
|
+
def old_server(port, **options):
|
|
956
|
+
calls.append((port, options))
|
|
957
|
+
raise withruntime.InvalidRequestError("Unknown expiresAt field", code="unknown_field", status=400)
|
|
958
|
+
native.get = old_server
|
|
959
|
+
with self.assertRaises(SandboxAPIError) as caught:
|
|
960
|
+
preview.tokens.create(deadline)
|
|
961
|
+
self.assertEqual((caught.exception.status_code, caught.exception.code), (400, "unknown_field"))
|
|
962
|
+
self.assertEqual(len(calls), 1)
|
|
963
|
+
self.assertNotIn("ttl_seconds", calls[0][1])
|
|
964
|
+
|
|
896
965
|
def test_what_a_runtime_preview_cannot_do(self):
|
|
897
966
|
box = self.create()
|
|
898
967
|
for field in ("responseHeaders", "customDomain", "ttl"):
|
|
@@ -988,7 +1057,20 @@ class Lifecycle(Base):
|
|
|
988
1057
|
self.create({"name": name})
|
|
989
1058
|
page = SyncSandboxInstance.list(limit=2)
|
|
990
1059
|
self.assertEqual(([one.metadata.name for one in page.data], page.has_more), (["a", "b"], True))
|
|
1060
|
+
self.assertTrue(page.meta.has_more)
|
|
1061
|
+
self.assertEqual(page.meta.next_cursor, page.next_cursor)
|
|
1062
|
+
self.assertIsNone(page.meta.total)
|
|
1063
|
+
self.assertIsNone(page.meta.total_is_partial)
|
|
1064
|
+
final = page.next_page()
|
|
1065
|
+
self.assertFalse(final.meta.has_more)
|
|
1066
|
+
self.assertIsNone(final.meta.next_cursor)
|
|
1067
|
+
self.assertNotIn('total', final.meta.to_dict())
|
|
991
1068
|
self.assertEqual([one.metadata.name for one in page.auto_paging_iter()], ["a", "b", "c"])
|
|
1069
|
+
page.meta.next_cursor = "changed-native-cursor"
|
|
1070
|
+
before = list(self.world.calls)
|
|
1071
|
+
with self.assertRaises(NotSupportedError):
|
|
1072
|
+
page.next_page()
|
|
1073
|
+
self.assertEqual(self.world.calls, before)
|
|
992
1074
|
with self.assertRaises(NotSupportedError):
|
|
993
1075
|
SyncSandboxInstance.list(cursor="abc")
|
|
994
1076
|
|
|
@@ -1026,6 +1108,33 @@ class Lifecycle(Base):
|
|
|
1026
1108
|
box.fork("x", **kwargs)
|
|
1027
1109
|
|
|
1028
1110
|
|
|
1111
|
+
class ForkLifecycle(Base):
|
|
1112
|
+
def test_sync_fork_lifecycle_override_refuses_before_lookup_or_mutation(self):
|
|
1113
|
+
box = SyncSandboxInstance({"metadata": {"name": "unresolved-source"}})
|
|
1114
|
+
for lifecycle in ({}, {"expirationPolicies": []}, bl.SandboxLifecycle.from_dict({})):
|
|
1115
|
+
for options in ({}, {"snapshot_id": "unresolved-snapshot"}):
|
|
1116
|
+
with self.subTest(lifecycle=lifecycle, options=options):
|
|
1117
|
+
with self.assertRaises(NotSupportedError) as caught:
|
|
1118
|
+
box.fork("copy", lifecycle=lifecycle, **options)
|
|
1119
|
+
self.assertIn("lifecycle", caught.exception.feature)
|
|
1120
|
+
self.assertEqual(caught.exception.alternative,
|
|
1121
|
+
"Omit lifecycle to use the existing Runtime fork behavior.")
|
|
1122
|
+
self.assertEqual(self.world.calls, [])
|
|
1123
|
+
|
|
1124
|
+
def test_sync_fork_default_and_explicit_none_pass_labels_without_override(self):
|
|
1125
|
+
lifecycle = {"expirationPolicies": [{"type": "ttl-idle", "value": "3d", "action": "delete"}]}
|
|
1126
|
+
box = self.create({"name": "lifecycle-source", "ttl": "5d", "lifecycle": lifecycle})
|
|
1127
|
+
labels = dict(self.runtime(box).info["labels"])
|
|
1128
|
+
default = box.fork("default-copy")
|
|
1129
|
+
explicit = box.fork("none-copy", lifecycle=None)
|
|
1130
|
+
self.assertEqual((default.name, explicit.name), ("default-copy", "none-copy"))
|
|
1131
|
+
forks = self.world.called("sandbox.fork")
|
|
1132
|
+
self.assertEqual(len(forks), 2)
|
|
1133
|
+
for _, fields in forks:
|
|
1134
|
+
self.assertEqual(fields["labels"], labels)
|
|
1135
|
+
self.assertNotIn("lifecycle", fields)
|
|
1136
|
+
|
|
1137
|
+
|
|
1029
1138
|
class Interpreter(Base):
|
|
1030
1139
|
def test_run_code_and_contexts(self):
|
|
1031
1140
|
interpreter = SyncCodeInterpreter.create({"name": "ci", "envs": [{"name": "K", "value": "v"}]})
|
|
@@ -1086,6 +1195,31 @@ class Unsupported(Base):
|
|
|
1086
1195
|
|
|
1087
1196
|
|
|
1088
1197
|
class Async(unittest.TestCase):
|
|
1198
|
+
def test_canceled_privileged_write_removes_its_staging_bytes(self):
|
|
1199
|
+
from withruntime.blaxel._async_sandbox import AsyncSandboxFileSystem
|
|
1200
|
+
async def scenario():
|
|
1201
|
+
paths = {}
|
|
1202
|
+
class Files:
|
|
1203
|
+
async def write(self, path, data):
|
|
1204
|
+
if path == '/root/private':
|
|
1205
|
+
raise withruntime.PermissionDeniedError('root only', code='permission_denied', status=403)
|
|
1206
|
+
paths[path] = data
|
|
1207
|
+
async def remove(self, path):
|
|
1208
|
+
paths.pop(path, None)
|
|
1209
|
+
files = Files()
|
|
1210
|
+
async def get_files():
|
|
1211
|
+
return files
|
|
1212
|
+
canceled = asyncio.CancelledError()
|
|
1213
|
+
async def fail(*args):
|
|
1214
|
+
raise canceled
|
|
1215
|
+
filesystem = AsyncSandboxFileSystem(None, None)
|
|
1216
|
+
filesystem._files, filesystem._sudo = get_files, fail
|
|
1217
|
+
with self.assertRaises(asyncio.CancelledError) as caught:
|
|
1218
|
+
await filesystem.write('/root/private', 'private bytes')
|
|
1219
|
+
self.assertIs(caught.exception, canceled)
|
|
1220
|
+
self.assertEqual(paths, {})
|
|
1221
|
+
asyncio.run(scenario())
|
|
1222
|
+
|
|
1089
1223
|
def setUp(self) -> None:
|
|
1090
1224
|
offline()
|
|
1091
1225
|
self.world = BlaxelWorld()
|
|
@@ -1096,6 +1230,95 @@ class Async(unittest.TestCase):
|
|
|
1096
1230
|
os.environ.clear()
|
|
1097
1231
|
os.environ.update(SAVED)
|
|
1098
1232
|
|
|
1233
|
+
def test_async_native_list_metadata_tracks_each_page_without_invented_totals(self):
|
|
1234
|
+
async def scenario():
|
|
1235
|
+
for name in ("a", "b", "c"):
|
|
1236
|
+
await SandboxInstance.create({"name": name})
|
|
1237
|
+
page = await SandboxInstance.list(limit=2)
|
|
1238
|
+
self.assertEqual([one.metadata.name for one in page.data], ["a", "b"])
|
|
1239
|
+
self.assertTrue(page.meta.has_more)
|
|
1240
|
+
self.assertEqual(page.meta.next_cursor, page.next_cursor)
|
|
1241
|
+
self.assertIsNone(page.meta.total)
|
|
1242
|
+
self.assertIsNone(page.meta.total_is_partial)
|
|
1243
|
+
final = await page.next_page()
|
|
1244
|
+
self.assertEqual([one.metadata.name for one in final.data], ["c"])
|
|
1245
|
+
self.assertFalse(final.meta.has_more)
|
|
1246
|
+
self.assertIsNone(final.meta.next_cursor)
|
|
1247
|
+
self.assertNotIn("total", final.meta.to_dict())
|
|
1248
|
+
page.meta.next_cursor = "changed-native-cursor"
|
|
1249
|
+
before = list(self.world.calls)
|
|
1250
|
+
with self.assertRaises(NotSupportedError):
|
|
1251
|
+
await page.next_page()
|
|
1252
|
+
self.assertEqual(self.world.calls, before)
|
|
1253
|
+
asyncio.run(scenario())
|
|
1254
|
+
|
|
1255
|
+
def test_async_invalid_token_expirations_refuse_before_lookup_or_native_requests(self):
|
|
1256
|
+
from withruntime.blaxel._async_sandbox import AsyncSandboxPreview
|
|
1257
|
+
async def scenario():
|
|
1258
|
+
box = SandboxInstance({"metadata": {"name": "unresolved-source"}})
|
|
1259
|
+
preview = AsyncSandboxPreview(bl.Preview.from_dict({
|
|
1260
|
+
"metadata": {"name": "app"}, "spec": {"port": 3000, "public": False}}), box)
|
|
1261
|
+
now = datetime.now(timezone.utc)
|
|
1262
|
+
for seconds in (-1, 0, 5, 59.999, 8 * 86400, 40 * 86400):
|
|
1263
|
+
with self.subTest(seconds=seconds), self.assertRaises(ValueError):
|
|
1264
|
+
await preview.tokens.create(now + timedelta(seconds=seconds))
|
|
1265
|
+
self.assertEqual(self.world.calls, [])
|
|
1266
|
+
for invalid in (None, "not-a-date", object(), datetime.now() + timedelta(minutes=10)):
|
|
1267
|
+
with self.assertRaises(ValueError):
|
|
1268
|
+
await preview.tokens.create(invalid)
|
|
1269
|
+
self.assertEqual(self.world.calls, [])
|
|
1270
|
+
asyncio.run(scenario())
|
|
1271
|
+
|
|
1272
|
+
def test_async_token_forwards_exact_deadline_and_refuses_extension(self):
|
|
1273
|
+
async def scenario():
|
|
1274
|
+
box = await SandboxInstance.create({"name": "web"})
|
|
1275
|
+
preview = await box.previews.create({"metadata": {"name": "app"}, "spec": {"port": 3000}})
|
|
1276
|
+
deadline = datetime.now(timezone.utc) + timedelta(minutes=10, microseconds=123)
|
|
1277
|
+
token = await preview.tokens.create(deadline)
|
|
1278
|
+
self.assertEqual(token.value, "tok-1")
|
|
1279
|
+
self.assertTrue(token.expires_at.endswith("Z"))
|
|
1280
|
+
self.assertLessEqual(datetime.fromisoformat(token.expires_at.replace("Z", "+00:00")), deadline)
|
|
1281
|
+
self.assertEqual(self.world.called("previews.get")[-1],
|
|
1282
|
+
(3000, None, deadline.isoformat(timespec="milliseconds").replace("+00:00", "Z")))
|
|
1283
|
+
calls = []
|
|
1284
|
+
def get(port, **options):
|
|
1285
|
+
calls.append((port, options))
|
|
1286
|
+
return {"token": "must-not-escape", "tokenExpiresAt": (deadline + timedelta(seconds=1)).isoformat()}
|
|
1287
|
+
self.world.sandboxes[box.withruntime.id].previews.get = get
|
|
1288
|
+
with self.assertRaises(SandboxAPIError):
|
|
1289
|
+
await preview.tokens.create(deadline)
|
|
1290
|
+
self.assertEqual(len(calls), 1)
|
|
1291
|
+
asyncio.run(scenario())
|
|
1292
|
+
|
|
1293
|
+
def test_async_fork_lifecycle_override_refuses_before_lookup_or_mutation(self):
|
|
1294
|
+
async def scenario():
|
|
1295
|
+
box = SandboxInstance({"metadata": {"name": "unresolved-source"}})
|
|
1296
|
+
for lifecycle in ({}, {"expirationPolicies": []}, bl.SandboxLifecycle.from_dict({})):
|
|
1297
|
+
for options in ({}, {"snapshot_id": "unresolved-snapshot"}):
|
|
1298
|
+
with self.subTest(lifecycle=lifecycle, options=options):
|
|
1299
|
+
with self.assertRaises(NotSupportedError) as caught:
|
|
1300
|
+
await box.fork("copy", lifecycle=lifecycle, **options)
|
|
1301
|
+
self.assertIn("lifecycle", caught.exception.feature)
|
|
1302
|
+
self.assertEqual(caught.exception.alternative,
|
|
1303
|
+
"Omit lifecycle to use the existing Runtime fork behavior.")
|
|
1304
|
+
self.assertEqual(self.world.calls, [])
|
|
1305
|
+
asyncio.run(scenario())
|
|
1306
|
+
|
|
1307
|
+
def test_async_fork_default_and_explicit_none_pass_labels_without_override(self):
|
|
1308
|
+
async def scenario():
|
|
1309
|
+
lifecycle = {"expirationPolicies": [{"type": "ttl-idle", "value": "3d", "action": "delete"}]}
|
|
1310
|
+
box = await SandboxInstance.create({"name": "lifecycle-source", "ttl": "5d", "lifecycle": lifecycle})
|
|
1311
|
+
labels = dict(self.world.sandboxes[box.withruntime.id].info["labels"])
|
|
1312
|
+
default = await box.fork("default-copy")
|
|
1313
|
+
explicit = await box.fork("none-copy", lifecycle=None)
|
|
1314
|
+
self.assertEqual((default.name, explicit.name), ("default-copy", "none-copy"))
|
|
1315
|
+
forks = self.world.called("sandbox.fork")
|
|
1316
|
+
self.assertEqual(len(forks), 2)
|
|
1317
|
+
for _, fields in forks:
|
|
1318
|
+
self.assertEqual(fields["labels"], labels)
|
|
1319
|
+
self.assertNotIn("lifecycle", fields)
|
|
1320
|
+
asyncio.run(scenario())
|
|
1321
|
+
|
|
1099
1322
|
def test_the_async_api(self):
|
|
1100
1323
|
async def scenario():
|
|
1101
1324
|
box = await SandboxInstance.create({"name": "a", "envs": [{"name": "A", "value": "1"}]})
|
|
@@ -1170,6 +1393,204 @@ class ImportPaths(unittest.TestCase):
|
|
|
1170
1393
|
self.assertTrue(caught.exception.alternative)
|
|
1171
1394
|
|
|
1172
1395
|
|
|
1396
|
+
class PreviewTokenExpiryShape(unittest.TestCase):
|
|
1397
|
+
def test_sync_utc_expiry_supports_the_python_310_iso_parser(self):
|
|
1398
|
+
from unittest.mock import patch
|
|
1399
|
+
from withruntime.blaxel._async_sandbox import AsyncSandboxPreviewToken
|
|
1400
|
+
calls = []
|
|
1401
|
+
class Python310Datetime:
|
|
1402
|
+
@staticmethod
|
|
1403
|
+
def fromisoformat(text):
|
|
1404
|
+
calls.append(text)
|
|
1405
|
+
if text.endswith('Z'):
|
|
1406
|
+
raise ValueError('Python 3.10 does not parse terminal Z')
|
|
1407
|
+
return datetime.fromisoformat(text)
|
|
1408
|
+
expiry = '2026-10-01T00:00:00.123Z'
|
|
1409
|
+
model = bl.PreviewToken(spec=bl.PreviewTokenSpec(token='private', expires_at=expiry))
|
|
1410
|
+
with patch('withruntime.blaxel._sync_io.datetime', Python310Datetime):
|
|
1411
|
+
parsed = _sync_sandbox.SandboxPreviewToken(model).expires_at
|
|
1412
|
+
self.assertEqual(parsed, datetime(2026, 10, 1, 0, 0, 0, 123000, tzinfo=timezone.utc))
|
|
1413
|
+
self.assertEqual(calls, ['2026-10-01T00:00:00.123+00:00'])
|
|
1414
|
+
self.assertIs(AsyncSandboxPreviewToken(model).expires_at, expiry)
|
|
1415
|
+
|
|
1416
|
+
def test_async_expiry_text_and_sync_datetime_match_consumer_expectations(self):
|
|
1417
|
+
from withruntime.blaxel._async_sandbox import AsyncSandboxPreviewToken
|
|
1418
|
+
for expiry in ('2026-10-01T00:00:00.123Z', '2026-10-01T05:30:00.123+05:30'):
|
|
1419
|
+
model = bl.PreviewToken(spec=bl.PreviewTokenSpec(token='private', expires_at=expiry))
|
|
1420
|
+
asynchronous = AsyncSandboxPreviewToken(model)
|
|
1421
|
+
synchronous = _sync_sandbox.SandboxPreviewToken(model)
|
|
1422
|
+
self.assertIs(asynchronous.expires_at, model.spec.expires_at)
|
|
1423
|
+
self.assertEqual(asynchronous.expires_at.endswith('Z'), expiry.endswith('Z'))
|
|
1424
|
+
self.assertEqual(synchronous.expires_at, datetime.fromisoformat(expiry.replace('Z', '+00:00')))
|
|
1425
|
+
self.assertEqual(synchronous.expires_at.utcoffset(), datetime.fromisoformat(expiry.replace('Z', '+00:00')).utcoffset())
|
|
1426
|
+
empty = bl.PreviewToken(spec=bl.PreviewTokenSpec())
|
|
1427
|
+
self.assertIsNone(AsyncSandboxPreviewToken(empty).expires_at)
|
|
1428
|
+
self.assertIsNone(_sync_sandbox.SandboxPreviewToken(empty).expires_at.utcoffset())
|
|
1429
|
+
no_spec = bl.PreviewToken()
|
|
1430
|
+
self.assertIsNone(AsyncSandboxPreviewToken(no_spec).expires_at.utcoffset())
|
|
1431
|
+
self.assertIsNone(_sync_sandbox.SandboxPreviewToken(no_spec).expires_at.utcoffset())
|
|
1432
|
+
|
|
1433
|
+
@unittest.skipUnless(os.environ.get('RUNTIME_COMPAT_OFFICIAL') == '1', 'Pinned official SDK opt-in')
|
|
1434
|
+
def test_pinned_official_unchanged_expiry_property_consumers(self):
|
|
1435
|
+
import importlib.metadata
|
|
1436
|
+
from blaxel.core.sandbox.default.preview import SandboxPreviewToken as OfficialAsync
|
|
1437
|
+
from blaxel.core.sandbox.sync.preview import SyncSandboxPreviewToken as OfficialSync
|
|
1438
|
+
from blaxel.core.client.models.preview_token import PreviewToken as OfficialToken
|
|
1439
|
+
from blaxel.core.client.models.preview_token_spec import PreviewTokenSpec as OfficialSpec
|
|
1440
|
+
self.assertEqual(importlib.metadata.version('blaxel'), '0.4.13')
|
|
1441
|
+
expiry = '2026-10-01T00:00:00.123Z'
|
|
1442
|
+
def async_consumer(token):
|
|
1443
|
+
return token.value, token.expires_at.endswith('Z'), token.expires_at
|
|
1444
|
+
def sync_consumer(token):
|
|
1445
|
+
return token.value, token.expires_at.isoformat(), token.expires_at.utcoffset()
|
|
1446
|
+
official = OfficialToken(spec=OfficialSpec(token='private', expires_at=expiry))
|
|
1447
|
+
migrated = bl.PreviewToken(spec=bl.PreviewTokenSpec(token='private', expires_at=expiry))
|
|
1448
|
+
self.assertEqual(async_consumer(OfficialAsync(official)), async_consumer(bl.SandboxPreviewToken(migrated)))
|
|
1449
|
+
self.assertEqual(sync_consumer(OfficialSync(official)), sync_consumer(_sync_sandbox.SandboxPreviewToken(migrated)))
|
|
1450
|
+
|
|
1451
|
+
|
|
1452
|
+
class NativePreviewDeadlineTransport(unittest.TestCase):
|
|
1453
|
+
def test_native_sync_and_async_forward_deadline_fields_and_preserve_existing_options(self):
|
|
1454
|
+
from types import SimpleNamespace
|
|
1455
|
+
from withruntime._sync_products.previews import Previews
|
|
1456
|
+
from withruntime._async_products.previews import AsyncPreviews
|
|
1457
|
+
deadline = '2026-10-01T00:00:00.123Z'
|
|
1458
|
+
calls = []
|
|
1459
|
+
class Transport:
|
|
1460
|
+
def json(self, method, path, **options):
|
|
1461
|
+
calls.append((method, path, options))
|
|
1462
|
+
return {}
|
|
1463
|
+
sync = Previews(Transport(), SimpleNamespace(id='box'))
|
|
1464
|
+
sync.get(3000)
|
|
1465
|
+
sync.get(3000, expires_at=deadline)
|
|
1466
|
+
sync.get(3000, ttl_seconds=120, expires_at=deadline)
|
|
1467
|
+
sync.create(3000, visibility='private', ttl_seconds=120, expires_at=deadline,
|
|
1468
|
+
embed_origins=['https://app.test'], idempotency_key='request')
|
|
1469
|
+
expected = [('GET', '/v1/sandboxes/box/previews/3000', {'query': None}),
|
|
1470
|
+
('GET', '/v1/sandboxes/box/previews/3000', {'query': {'expiresAt': deadline}}),
|
|
1471
|
+
('GET', '/v1/sandboxes/box/previews/3000', {'query': {'ttlSeconds': 120, 'expiresAt': deadline}}),
|
|
1472
|
+
('POST', '/v1/sandboxes/box/previews',
|
|
1473
|
+
{'body': {'port': 3000, 'visibility': 'private', 'ttlSeconds': 120, 'expiresAt': deadline,
|
|
1474
|
+
'embedOrigins': ['https://app.test']}, 'idempotency_key': 'request'})]
|
|
1475
|
+
self.assertEqual(calls, expected)
|
|
1476
|
+
calls.clear()
|
|
1477
|
+
class AsyncTransport:
|
|
1478
|
+
async def json(self, method, path, **options):
|
|
1479
|
+
calls.append((method, path, options))
|
|
1480
|
+
return {}
|
|
1481
|
+
async def run():
|
|
1482
|
+
native = AsyncPreviews(AsyncTransport(), SimpleNamespace(id='box'))
|
|
1483
|
+
await native.get(3000)
|
|
1484
|
+
await native.get(3000, expires_at=deadline)
|
|
1485
|
+
await native.get(3000, ttl_seconds=120, expires_at=deadline)
|
|
1486
|
+
await native.create(3000, visibility='private', ttl_seconds=120, expires_at=deadline,
|
|
1487
|
+
embed_origins=['https://app.test'], idempotency_key='request')
|
|
1488
|
+
asyncio.run(run())
|
|
1489
|
+
self.assertEqual(calls, expected)
|
|
1490
|
+
|
|
1491
|
+
|
|
1492
|
+
class PaginationMetadata(unittest.TestCase):
|
|
1493
|
+
def test_official_metadata_imports_resolve_to_the_same_model(self):
|
|
1494
|
+
from withruntime.blaxel.core.client.models import PaginationMeta
|
|
1495
|
+
from withruntime.blaxel.core.client.models.pagination_meta import PaginationMeta as FromModule
|
|
1496
|
+
self.assertIs(PaginationMeta, bl.PaginationMeta)
|
|
1497
|
+
self.assertIs(FromModule, bl.PaginationMeta)
|
|
1498
|
+
from withruntime.blaxel.core.client.pagination import AsyncPaginatedList, PaginatedList
|
|
1499
|
+
self.assertIs(PaginatedList, bl.PaginatedList)
|
|
1500
|
+
self.assertIs(AsyncPaginatedList, bl.AsyncPaginatedList)
|
|
1501
|
+
|
|
1502
|
+
@unittest.skipUnless(os.environ.get('RUNTIME_COMPAT_OFFICIAL') == '1', 'Pinned official SDK opt-in')
|
|
1503
|
+
def test_pinned_official_unchanged_pagination_consumers(self):
|
|
1504
|
+
import importlib
|
|
1505
|
+
import importlib.metadata
|
|
1506
|
+
self.assertEqual(importlib.metadata.version('blaxel'), '0.4.13')
|
|
1507
|
+
def sync_consumer(pages, model):
|
|
1508
|
+
seen = []
|
|
1509
|
+
meta = model(has_more=True, next_cursor='before')
|
|
1510
|
+
final = pages.PaginatedList([2], meta=model(has_more=False))
|
|
1511
|
+
page = pages.PaginatedList([1], meta=meta, fetch_next=lambda cursor: seen.append(cursor) or final)
|
|
1512
|
+
self.assertIs(page.meta, meta)
|
|
1513
|
+
self.assertIs(page.data, page)
|
|
1514
|
+
meta.next_cursor = 'after'
|
|
1515
|
+
values = list(page.auto_paging_iter())
|
|
1516
|
+
return values, seen, meta.to_dict(), final.has_more, final.next_cursor
|
|
1517
|
+
async def async_consumer(pages, model):
|
|
1518
|
+
seen = []
|
|
1519
|
+
meta = model(has_more=True, next_cursor='before')
|
|
1520
|
+
final = pages.AsyncPaginatedList([2], meta=model(has_more=False))
|
|
1521
|
+
async def fetch(cursor):
|
|
1522
|
+
seen.append(cursor)
|
|
1523
|
+
return final
|
|
1524
|
+
page = pages.AsyncPaginatedList([1], meta=meta, fetch_next=fetch)
|
|
1525
|
+
self.assertIs(page.meta, meta)
|
|
1526
|
+
self.assertIs(page.data, page)
|
|
1527
|
+
meta.next_cursor = 'after'
|
|
1528
|
+
values = [value async for value in page.auto_paging_iter()]
|
|
1529
|
+
return values, seen, meta.to_dict(), final.has_more, final.next_cursor
|
|
1530
|
+
results = []
|
|
1531
|
+
for prefix in ('blaxel', 'withruntime.blaxel'):
|
|
1532
|
+
pages = importlib.import_module(prefix + '.core.client.pagination')
|
|
1533
|
+
model = importlib.import_module(prefix + '.core.client.models.pagination_meta').PaginationMeta
|
|
1534
|
+
results.append((sync_consumer(pages, model), asyncio.run(async_consumer(pages, model))))
|
|
1535
|
+
expected = ([1, 2], ['after'], {'hasMore': True, 'nextCursor': 'after'}, False, None)
|
|
1536
|
+
self.assertEqual(results, [(expected, expected), (expected, expected)])
|
|
1537
|
+
|
|
1538
|
+
def test_model_aliases_unknown_fields_and_independent_defaults(self):
|
|
1539
|
+
first = bl.PaginationMeta.from_dict({'hasMore': True, 'nextCursor': 'opaque', 'totalIsPartial': True, 'vendor': 1})
|
|
1540
|
+
second = bl.PaginationMeta.from_dict({'has_more': False, 'total': 3})
|
|
1541
|
+
self.assertEqual((first.has_more, first.next_cursor, first.total, first.total_is_partial), (True, 'opaque', None, True))
|
|
1542
|
+
self.assertEqual(first.to_dict(), {'hasMore': True, 'nextCursor': 'opaque', 'totalIsPartial': True, 'vendor': 1})
|
|
1543
|
+
self.assertEqual(second.to_dict(), {'hasMore': False, 'total': 3})
|
|
1544
|
+
self.assertEqual(first['vendor'], 1)
|
|
1545
|
+
first['changed'] = 2
|
|
1546
|
+
self.assertNotIn('changed', second)
|
|
1547
|
+
self.assertEqual(first.additional_keys, ['vendor', 'changed'])
|
|
1548
|
+
del first['changed']
|
|
1549
|
+
self.assertEqual(first.additional_keys, ['vendor'])
|
|
1550
|
+
self.assertIsNone(bl.PaginationMeta.from_dict({}))
|
|
1551
|
+
self.assertEqual(bl.PaginationMeta().to_dict(), {})
|
|
1552
|
+
self.assertEqual(bl.PaginationMeta(has_more=core.UNSET).to_dict(), {})
|
|
1553
|
+
aliases = bl.PaginationMeta.from_dict({'hasMore': False, 'has_more': True,
|
|
1554
|
+
'nextCursor': 'first', 'next_cursor': 'second'})
|
|
1555
|
+
self.assertEqual(aliases.to_dict(), {'hasMore': False, 'nextCursor': 'first'})
|
|
1556
|
+
|
|
1557
|
+
def test_sync_custom_metadata_identity_mutable_cursor_and_dict_behavior(self):
|
|
1558
|
+
seen = []
|
|
1559
|
+
meta = bl.PaginationMeta(has_more=True, next_cursor='original')
|
|
1560
|
+
expected = bl.PaginatedList([2])
|
|
1561
|
+
page = bl.PaginatedList([1], meta=meta, fetch_next=lambda cursor: seen.append(cursor) or expected)
|
|
1562
|
+
self.assertIs(page.meta, meta)
|
|
1563
|
+
self.assertIs(page.data, page)
|
|
1564
|
+
meta.next_cursor = 'changed'
|
|
1565
|
+
self.assertIs(page.next_page(), expected)
|
|
1566
|
+
self.assertEqual(seen, ['changed'])
|
|
1567
|
+
meta.has_more = False
|
|
1568
|
+
self.assertFalse(page.has_more)
|
|
1569
|
+
self.assertEqual(page.next_page(), [])
|
|
1570
|
+
raw = {'has_more': True, 'next_cursor': 'ignored'}
|
|
1571
|
+
direct = bl.PaginatedList([1], meta=raw, fetch_next=lambda _: self.fail('dict metadata must not fetch'))
|
|
1572
|
+
self.assertIs(direct.meta, raw)
|
|
1573
|
+
self.assertFalse(direct.has_more)
|
|
1574
|
+
self.assertEqual(direct.next_page(), [])
|
|
1575
|
+
|
|
1576
|
+
def test_async_custom_metadata_identity_and_mutable_cursor(self):
|
|
1577
|
+
async def run():
|
|
1578
|
+
seen = []
|
|
1579
|
+
meta = bl.PaginationMeta(has_more=True, next_cursor='original')
|
|
1580
|
+
expected = bl.AsyncPaginatedList([2])
|
|
1581
|
+
async def fetch(cursor):
|
|
1582
|
+
seen.append(cursor)
|
|
1583
|
+
return expected
|
|
1584
|
+
page = bl.AsyncPaginatedList([1], meta=meta, fetch_next=fetch)
|
|
1585
|
+
self.assertIs(page.meta, meta)
|
|
1586
|
+
meta.next_cursor = 'changed'
|
|
1587
|
+
self.assertIs(await page.next_page(), expected)
|
|
1588
|
+
self.assertEqual(seen, ['changed'])
|
|
1589
|
+
meta.has_more = False
|
|
1590
|
+
self.assertEqual(await page.next_page(), [])
|
|
1591
|
+
asyncio.run(run())
|
|
1592
|
+
|
|
1593
|
+
|
|
1173
1594
|
class Generated(unittest.TestCase):
|
|
1174
1595
|
def test_the_sync_adapter_is_generated_from_the_async_one(self):
|
|
1175
1596
|
result = subprocess.run([sys.executable, str(ROOT / "scripts" / "generate_dropin_sync.py"), "--check"],
|