pyo3-async-runtimes-0.22.0/.cargo_vcs_info.json0000644000000001360000000000100147660ustar { "git": { "sha1": "41d0fe3bedfe4feb34229973b4011f7cbe26dcf6" }, "path_in_vcs": "" }pyo3-async-runtimes-0.22.0/.devcontainer/Dockerfile000064400000000000000000000007201046102023000203060ustar 00000000000000# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/rust/.devcontainer/base.Dockerfile # [Choice] Debian OS version (use bullseye on local arm64/Apple Silicon): buster, bullseye ARG VARIANT="buster" FROM mcr.microsoft.com/vscode/devcontainers/rust:0-${VARIANT} RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install python3-dev python3-pip RUN python3 -m pip install uvlooppyo3-async-runtimes-0.22.0/.devcontainer/devcontainer.json000064400000000000000000000031301046102023000216660ustar 00000000000000// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/rust { "name": "Rust", "build": { "dockerfile": "Dockerfile", "args": { // Use the VARIANT arg to pick a Debian OS version: buster, bullseye // Use bullseye when on local on arm64/Apple Silicon. "VARIANT": "buster" } }, "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], // Configure tool-specific properties. "customizations": { // Configure properties specific to VS Code. "vscode": { // Set *default* container specific settings.json values on container create. "settings": { "lldb.executable": "/usr/bin/lldb", // VS Code don't watch files under ./target "files.watcherExclude": { "**/target/**": true }, "rust-analyzer.checkOnSave.command": "clippy" }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ "vadimcn.vscode-lldb", "mutantdino.resourcemonitor", "rust-lang.rust-analyzer", "tamasfe.even-better-toml", "serayuzgur.crates" ] } }, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "rustc --version", // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode", "features": { "git": "latest", "git-lfs": "latest" } } pyo3-async-runtimes-0.22.0/.githooks/pre-commit000075500000000000000000000000651046102023000174660ustar 00000000000000#!/bin/bash cargo check --all-targets --all-featurespyo3-async-runtimes-0.22.0/.githooks/pre-push000075500000000000000000000000421046102023000171500ustar 00000000000000#!/bin/bash make clippy make testpyo3-async-runtimes-0.22.0/.github/dependabot.yml000064400000000000000000000011521046102023000177450ustar 00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "cargo" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" allow: - dependency-type: direct - dependency-name: tokio # haven't been seeing updates for tokio pyo3-async-runtimes-0.22.0/.github/issue_template.md000064400000000000000000000013401046102023000204610ustar 00000000000000## 🐛 Bug Reports When reporting a bug, please provide the following information. If this is not a bug report you can just discard this template. ### 🌍 Environment - Your operating system and version: - Your python version: - How did you install python (e.g. apt or pyenv)? Did you use a virtualenv?: - Your Rust version (`rustc --version`): - Your PyO3 version: - Have you tried using latest PyO3 master (replace `version = "0.x.y"` with `git = "https://github.com/PyO3/pyo3-async-runtimes")?`: ### 💥 Reproducing Please provide a [minimal working example](https://stackoverflow.com/help/mcve). This means both the Rust code and the Python. Please also write what exact flags are required to reproduce your results. pyo3-async-runtimes-0.22.0/.github/pull_request_template.md000064400000000000000000000007631046102023000220650ustar 00000000000000Thank you for contributing to pyo3-async-runtimes! Please consider adding the following to your pull request: - an entry in CHANGELOG.md - docs to all new functions and / or detail in the guide - tests for all new or changed functions Be aware the CI pipeline will check your pull request for the following: - Rust tests (Just `cargo test`) - Rust lints (`make clippy`) - Rust formatting (`cargo fmt`) - Python formatting (`black . --check`. You can install black with `pip install black`) pyo3-async-runtimes-0.22.0/.github/workflows/ci.yml000064400000000000000000000124751046102023000203020ustar 00000000000000name: CI on: push: branches: - main pull_request: branches: - main env: CARGO_TERM_COLOR: always jobs: fmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.13" - run: pip install black==24.10.0 - uses: dtolnay/rust-toolchain@stable with: toolchain: stable components: rustfmt - name: Check python formatting (black) run: black --check . - name: Check rust formatting (rustfmt) run: cargo fmt --all -- --check clippy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: toolchain: stable components: clippy - run: make clippy build: needs: [fmt] # don't wait for clippy as fails rarely and takes longer name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} ${{ matrix.msrv }} runs-on: ${{ matrix.platform.os }} strategy: fail-fast: false # If one platform fails, allow the rest to keep testing. matrix: rust: [stable] python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "pypy-3.9"] platform: [ { os: "macos-latest", python-architecture: "arm64", rust-target: "aarch64-apple-darwin", }, { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu", }, { os: "windows-latest", python-architecture: "x64", rust-target: "x86_64-pc-windows-msvc", }, { os: "windows-latest", python-architecture: "x86", rust-target: "i686-pc-windows-msvc", }, ] exclude: # PyPy doesn't release 32-bit Windows builds any more - python-version: pypy-3.9 platform: { os: "windows-latest", python-architecture: "x86" } include: # Test minimal supported Rust version - rust: 1.63.0 python-version: "3.13" platform: { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu", } msrv: "MSRV" # Test the `nightly` feature - rust: nightly python-version: "3.12" platform: { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu", } msrv: "nightly" extra_features: "nightly" steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.platform.python-architecture }} - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.rust }} target: ${{ matrix.platform.rust-target }} - if: ${{ matrix.msrv == 'MSRV' }} name: Set MSRV dependencies run: | cargo add tokio@=1.38.1 - name: Build (no features) run: cargo build --no-default-features --verbose --target ${{ matrix.platform.rust-target }} - name: Build run: cargo build --features=${{env.features}} --verbose --target ${{ matrix.platform.rust-target }} # uvloop doesn't compile under Windows and PyPy - if: ${{ matrix.platform.os != 'windows-latest' && !startsWith(matrix.python-version, 'pypy') }} name: Install pyo3-asyncio test dependencies run: | python -m pip install -U uvloop - if: ${{ matrix.msrv != 'MSRV' && !startsWith(matrix.python-version, 'pypy') }} name: Test run: cargo test --all-features --target ${{ matrix.platform.rust-target }} - if: ${{ matrix.msrv == 'MSRV' && !startsWith(matrix.python-version, 'pypy') }} name: Test (MSRV, --no-default-features) run: cargo test --no-default-features --features tokio-runtime,async-std-runtime,attributes,unstable-streams --target ${{ matrix.platform.rust-target }} env: RUST_BACKTRACE: 1 RUSTFLAGS: "-D warnings" coverage: needs: [fmt] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: toolchain: nightly override: true - name: Install pyo3-asyncio test dependencies run: | python -m pip install -U uvloop - uses: actions-rs/cargo@v1 with: command: test args: --all-features env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Cllvm-args=--inline-threshold=0 -Clink-dead-code -Coverflow-checks=off" RUSTDOCFLAGS: "-Zprofile -Ccodegen-units=1 -Cllvm-args=--inline-threshold=0 -Clink-dead-code -Coverflow-checks=off" - uses: actions-rs/grcov@v0.1 id: coverage - uses: codecov/codecov-action@v4 with: file: ${{ steps.coverage.outputs.report }} pyo3-async-runtimes-0.22.0/.github/workflows/guide.yml000064400000000000000000000033431046102023000207760ustar 00000000000000name: gh-pages on: push: branches: - master release: types: [published] env: CARGO_TERM_COLOR: always jobs: deploy: runs-on: ubuntu-latest outputs: tag_name: ${{ steps.prepare_tag.outputs.tag_name }} steps: - uses: actions/checkout@v3 # This adds the docs to gh-pages-build/doc - name: Build the doc run: | cargo doc --no-deps --all-features mkdir -p gh-pages-build cp -r target/doc gh-pages-build/doc echo "" > gh-pages-build/doc/index.html - name: Prepare tag id: prepare_tag run: | TAG_NAME="${GITHUB_REF##*/}" echo "::set-output name=tag_name::${TAG_NAME}" - name: Deploy uses: peaceiris/actions-gh-pages@v3.7.0-8 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./gh-pages-build/ destination_dir: ${{ steps.prepare_tag.outputs.tag_name }} full_commit_message: "Upload documentation for ${{ steps.prepare_tag.outputs.tag_name }}" release: needs: deploy runs-on: ubuntu-latest if: ${{ github.event_name == 'release' }} steps: - name: Create latest tag redirect env: TAG_NAME: ${{ needs.deploy.outputs.tag_name }} run: | mkdir public echo "" > public/index.html - name: Deploy uses: peaceiris/actions-gh-pages@v3.7.0-8 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./public/ full_commit_message: "Release ${{ needs.deploy.outputs.tag_name }}" keep_files: true pyo3-async-runtimes-0.22.0/Cargo.lock0000644000000635760000000000100127620ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "anstream" version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" [[package]] name = "anstyle-parse" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", "windows-sys 0.59.0", ] [[package]] name = "async-channel" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", "event-listener 2.5.3", "futures-core", ] [[package]] name = "async-channel" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", "slab", ] [[package]] name = "async-global-executor" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.3.1", "async-executor", "async-io", "async-lock", "blocking", "futures-lite", "once_cell", ] [[package]] name = "async-io" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ "async-lock", "cfg-if", "concurrent-queue", "futures-io", "futures-lite", "parking", "polling", "rustix", "slab", "tracing", "windows-sys 0.59.0", ] [[package]] name = "async-lock" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ "event-listener 5.3.1", "event-listener-strategy", "pin-project-lite", ] [[package]] name = "async-process" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ "async-channel 2.3.1", "async-io", "async-lock", "async-signal", "async-task", "blocking", "cfg-if", "event-listener 5.3.1", "futures-lite", "rustix", "tracing", ] [[package]] name = "async-signal" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ "async-io", "async-lock", "atomic-waker", "cfg-if", "futures-core", "futures-io", "rustix", "signal-hook-registry", "slab", "windows-sys 0.59.0", ] [[package]] name = "async-std" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" dependencies = [ "async-channel 1.9.0", "async-global-executor", "async-io", "async-lock", "async-process", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", "futures-lite", "gloo-timers", "kv-log-macro", "log", "memchr", "once_cell", "pin-project-lite", "pin-utils", "slab", "wasm-bindgen-futures", ] [[package]] name = "async-task" version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets", ] [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "blocking" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ "async-channel 2.3.1", "async-task", "futures-io", "futures-lite", "piper", ] [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_lex" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "event-listener" version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] [[package]] name = "event-listener-strategy" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ "event-listener 5.3.1", "pin-project-lite", ] [[package]] name = "fastrand" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "futures" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "fastrand", "futures-core", "futures-io", "parking", "pin-project-lite", ] [[package]] name = "futures-macro" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gloo-timers" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ "futures-channel", "futures-core", "js-sys", "wasm-bindgen", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "indoc" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "inventory" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "js-sys" version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] [[package]] name = "kv-log-macro" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ "log", ] [[package]] name = "libc" version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" dependencies = [ "value-bag", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "miniz_oxide" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", ] [[package]] name = "object" version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" dependencies = [ "portable-atomic", ] [[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", "fastrand", "futures-io", ] [[package]] name = "polling" version = "3.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", "rustix", "tracing", "windows-sys 0.59.0", ] [[package]] name = "portable-atomic" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", "unindent", ] [[package]] name = "pyo3-async-runtimes" version = "0.22.0" dependencies = [ "async-channel 2.3.1", "async-std", "clap", "futures", "inventory", "once_cell", "pin-project-lite", "pyo3", "pyo3-async-runtimes-macros", "tokio", ] [[package]] name = "pyo3-async-runtimes-macros" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22c26fd8e9fc19f53f0c1e00bf61471de6789f7eb263056f7f944a9cceb5823e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "pyo3-build-config" version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc38c5feeb496c8321091edf3d63e9a6829eab4b863b4a6a65f26f3e9cc6b179" dependencies = [ "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94845622d88ae274d2729fcefc850e63d7a3ddff5e3ce11bd88486db9f1d357d" dependencies = [ "libc", "pyo3-build-config", ] [[package]] name = "pyo3-macros" version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e655aad15e09b94ffdb3ce3d217acf652e26bbc37697ef012f5e5e348c716e5e" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", "syn", ] [[package]] name = "pyo3-macros-backend" version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1e3f09eecd94618f60a455a23def79f79eba4dc561a97324bf9ac8c6df30ce" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", "syn", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tokio" version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "pin-project-lite", ] [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unindent" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "value-bag" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" [[package]] name = "wasm-bindgen" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" pyo3-async-runtimes-0.22.0/Cargo.toml0000644000000111570000000000100127710ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.63" name = "pyo3-async-runtimes" version = "0.22.0" authors = [ "Andrew J Westlake ", "David Hewitt ", ] build = false exclude = [ "/.gitignore", "/codecov.yml", "/Makefile", ] autobins = false autoexamples = false autotests = false autobenches = false description = "PyO3 bridges from Rust runtimes to Python's Asyncio library" homepage = "https://github.com/PyO3/pyo3-async-runtimes" documentation = "https://docs.rs/crate/pyo3-async-runtimes/" readme = "README.md" keywords = [ "pyo3", "python", "ffi", "async", "asyncio", ] categories = [ "api-bindings", "development-tools::ffi", ] license = "Apache-2.0" repository = "https://github.com/PyO3/pyo3-async-runtimes" [package.metadata.docs.rs] features = [ "attributes", "testing", "async-std-runtime", "tokio-runtime", ] [lib] name = "pyo3_async_runtimes" path = "src/lib.rs" [[example]] name = "async_std" path = "examples/async_std.rs" required-features = [ "attributes", "async-std-runtime", ] [[example]] name = "tokio" path = "examples/tokio.rs" required-features = [ "attributes", "tokio-runtime", ] [[example]] name = "tokio_current_thread" path = "examples/tokio_current_thread.rs" required-features = [ "attributes", "tokio-runtime", ] [[example]] name = "tokio_multi_thread" path = "examples/tokio_multi_thread.rs" required-features = [ "attributes", "tokio-runtime", ] [[test]] name = "test_async_std_asyncio" path = "pytests/test_async_std_asyncio.rs" harness = false required-features = [ "async-std-runtime", "testing", "attributes", ] [[test]] name = "test_async_std_run_forever" path = "pytests/test_async_std_run_forever.rs" harness = false required-features = [ "async-std-runtime", "testing", ] [[test]] name = "test_async_std_uvloop" path = "pytests/test_async_std_uvloop.rs" harness = false required-features = [ "async-std-runtime", "testing", ] [[test]] name = "test_race_condition_regression" path = "pytests/test_race_condition_regression.rs" harness = false required-features = [ "async-std-runtime", "testing", ] [[test]] name = "test_tokio_current_thread_asyncio" path = "pytests/test_tokio_current_thread_asyncio.rs" harness = false required-features = [ "tokio-runtime", "testing", "attributes", ] [[test]] name = "test_tokio_current_thread_run_forever" path = "pytests/test_tokio_current_thread_run_forever.rs" harness = false required-features = [ "tokio-runtime", "testing", ] [[test]] name = "test_tokio_current_thread_uvloop" path = "pytests/test_tokio_current_thread_uvloop.rs" harness = false required-features = [ "tokio-runtime", "testing", ] [[test]] name = "test_tokio_multi_thread_asyncio" path = "pytests/test_tokio_multi_thread_asyncio.rs" harness = false required-features = [ "tokio-runtime", "testing", "attributes", ] [[test]] name = "test_tokio_multi_thread_run_forever" path = "pytests/test_tokio_multi_thread_run_forever.rs" harness = false required-features = [ "tokio-runtime", "testing", ] [[test]] name = "test_tokio_multi_thread_uvloop" path = "pytests/test_tokio_multi_thread_uvloop.rs" harness = false required-features = [ "tokio-runtime", "testing", ] [dependencies.async-channel] version = "2.3" optional = true [dependencies.async-std] version = "1.12" features = ["unstable"] optional = true [dependencies.clap] version = "4.5" optional = true [dependencies.futures] version = "0.3" [dependencies.inventory] version = "0.3" optional = true [dependencies.once_cell] version = "1.14" [dependencies.pin-project-lite] version = "0.2" [dependencies.pyo3] version = "0.22" [dependencies.pyo3-async-runtimes-macros] version = "=0.22.0" optional = true [dependencies.tokio] version = "1.13" features = [ "rt", "rt-multi-thread", "time", ] optional = true [dev-dependencies.pyo3] version = "0.22" features = ["macros"] [features] async-std-runtime = ["async-std"] attributes = ["pyo3-async-runtimes-macros"] default = [] testing = [ "clap", "inventory", ] tokio-runtime = ["tokio"] unstable-streams = ["async-channel"] pyo3-async-runtimes-0.22.0/Cargo.toml.orig000064400000000000000000000073661046102023000164610ustar 00000000000000[package] name = "pyo3-async-runtimes" description = "PyO3 bridges from Rust runtimes to Python's Asyncio library" version = "0.22.0" authors = [ "Andrew J Westlake ", "David Hewitt ", ] readme = "README.md" keywords = ["pyo3", "python", "ffi", "async", "asyncio"] homepage = "https://github.com/PyO3/pyo3-async-runtimes" repository = "https://github.com/PyO3/pyo3-async-runtimes" documentation = "https://docs.rs/crate/pyo3-async-runtimes/" categories = ["api-bindings", "development-tools::ffi"] license = "Apache-2.0" exclude = ["/.gitignore", "/codecov.yml", "/Makefile"] edition = "2021" rust-version = "1.63" [workspace] members = ["pyo3-async-runtimes-macros"] [features] async-std-runtime = ["async-std"] attributes = ["pyo3-async-runtimes-macros"] testing = ["clap", "inventory"] tokio-runtime = ["tokio"] unstable-streams = ["async-channel"] default = [] [package.metadata.docs.rs] features = ["attributes", "testing", "async-std-runtime", "tokio-runtime"] [[example]] name = "async_std" path = "examples/async_std.rs" required-features = ["attributes", "async-std-runtime"] [[example]] name = "tokio" path = "examples/tokio.rs" required-features = ["attributes", "tokio-runtime"] [[example]] name = "tokio_current_thread" path = "examples/tokio_current_thread.rs" required-features = ["attributes", "tokio-runtime"] [[example]] name = "tokio_multi_thread" path = "examples/tokio_multi_thread.rs" required-features = ["attributes", "tokio-runtime"] [[test]] name = "test_async_std_asyncio" path = "pytests/test_async_std_asyncio.rs" harness = false required-features = ["async-std-runtime", "testing", "attributes"] [[test]] name = "test_async_std_run_forever" path = "pytests/test_async_std_run_forever.rs" harness = false required-features = ["async-std-runtime", "testing"] [[test]] name = "test_tokio_current_thread_asyncio" path = "pytests/test_tokio_current_thread_asyncio.rs" harness = false required-features = ["tokio-runtime", "testing", "attributes"] [[test]] name = "test_tokio_current_thread_run_forever" path = "pytests/test_tokio_current_thread_run_forever.rs" harness = false required-features = ["tokio-runtime", "testing"] [[test]] name = "test_tokio_multi_thread_asyncio" path = "pytests/test_tokio_multi_thread_asyncio.rs" harness = false required-features = ["tokio-runtime", "testing", "attributes"] [[test]] name = "test_tokio_multi_thread_run_forever" path = "pytests/test_tokio_multi_thread_run_forever.rs" harness = false required-features = ["tokio-runtime", "testing"] [[test]] name = "test_async_std_uvloop" path = "pytests/test_async_std_uvloop.rs" harness = false required-features = ["async-std-runtime", "testing"] [[test]] name = "test_tokio_current_thread_uvloop" path = "pytests/test_tokio_current_thread_uvloop.rs" harness = false required-features = ["tokio-runtime", "testing"] [[test]] name = "test_tokio_multi_thread_uvloop" path = "pytests/test_tokio_multi_thread_uvloop.rs" harness = false required-features = ["tokio-runtime", "testing"] [[test]] name = "test_race_condition_regression" path = "pytests/test_race_condition_regression.rs" harness = false required-features = ["async-std-runtime", "testing"] [dependencies] async-channel = { version = "2.3", optional = true } clap = { version = "4.5", optional = true } futures = "0.3" inventory = { version = "0.3", optional = true } once_cell = "1.14" pin-project-lite = "0.2" pyo3 = "0.22" pyo3-async-runtimes-macros = { path = "pyo3-async-runtimes-macros", version = "=0.22.0", optional = true } [dev-dependencies] pyo3 = { version = "0.22", features = ["macros"] } [dependencies.async-std] version = "1.12" features = ["unstable"] optional = true [dependencies.tokio] version = "1.13" features = ["rt", "rt-multi-thread", "time"] optional = true pyo3-async-runtimes-0.22.0/Code-of-Conduct.md000064400000000000000000000064041046102023000167550ustar 00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq pyo3-async-runtimes-0.22.0/Contributing.md000064400000000000000000000057331046102023000165570ustar 00000000000000# Contributing Thank you for your interest in contributing to PyO3! All are welcome - please consider reading our [Code of Conduct](Code-of-Conduct.md) to keep our community positive and inclusive. If you are searching for ideas how to contribute, please read the "Getting started contributing" section. Once you've found an issue to contribute to, you may find the section "Writing pull requests" helpful. ## Getting started contributing Please join in with any part of PyO3 which interests you. We use Github issues to record all bugs and ideas. Feel free to request an issue to be assigned to you if you want to work on it. The following sections also contain specific ideas on where to start contributing to PyO3. ### Help users identify bugs The [PyO3 Gitter channel](https://gitter.im/PyO3/Lobby) is very active with users who are new to PyO3, and often completely new to Rust. Helping them debug is a great way to get experience with the PyO3 codebase. Helping others often reveals bugs, documentation weaknesses, and missing APIs. It's a good idea to open Github issues for these immediately so the resolution can be designed and implemented! ### Review pull requests Everybody is welcome to submit comments on open PRs. Please help ensure new PyO3 APIs are safe, performant, tidy, and easy to use! ## Writing pull requests Here are a few things to note when you are writing PRs. ### Continuous Integration The PyO3 Asyncio repo uses Github Actions. PRs are blocked from merging if CI is not successful. Formatting, linting and tests are checked for all Rust and Python code. In addition, all warnings in Rust code are disallowed (using `RUSTFLAGS="-D warnings"`). Tests run with all supported Python versions with the latest stable Rust compiler, as well as for Python 3.9 with the minimum supported Rust version. ### Minimum supported Rust version PyO3 aims to make use of up-to-date Rust language features to keep the implementation as efficient as possible. However, there will always be support for at least the last few Rust compiler versions, so that users have time to update. If your PR needs to bump the minimum supported Rust version, this is acceptable with the following conditions: - Any changes which require a more recent version than what is [currently available on stable Red Hat Enterprise Linux](https://access.redhat.com/documentation/en-us/red_hat_developer_tools/1/) will be postponed. (This is to allow package managers to update support for newer `rustc` versions; RHEL was arbitrarily picked because their update policy is clear.) - You might be asked to do extra work to tidy up other parts of the PyO3 codebase which can use the compiler version bump :) ## Git Hooks (Recommended) Using the project's githooks are recommended to prevent CI from failing for trivial reasons such as build checks or integration tests failing. Enabling the hooks should run `cargo check --all-targets` for every commit and `cargo test` for every push. ``` git config core.hookspath .githooks ```pyo3-async-runtimes-0.22.0/LICENSE000064400000000000000000000250351046102023000145700ustar 00000000000000 Copyright (c) 2017-present PyO3 Project and Contributors. https://github.com/PyO3 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. pyo3-async-runtimes-0.22.0/README.md000064400000000000000000000677161046102023000150560ustar 00000000000000# PyO3 Asyncio [![Actions Status](https://github.com/PyO3/pyo3-async-runtimes/workflows/CI/badge.svg)](https://github.com/PyO3/pyo3-async-runtimes) [![codecov](https://codecov.io/gh/davidhewitt/pyo3-async-runtimes/branch/main/graph/badge.svg)](https://codecov.io/gh/PyO3/pyo3-async-runtimes) [![crates.io](https://img.shields.io/crates/v/pyo3-async-runtimes)](https://crates.io/crates/pyo3-async-runtimes) [![minimum rustc 1.63](https://img.shields.io/badge/rustc-1.63+-blue.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) ***This is a fork of [`pyo3-asyncio`](https://github.com/awestlake87/pyo3-asyncio/) to deliver compatibility for PyO3 0.21. This may be the base for a permanent fork in the future, depending on the status of the original `pyo3-asyncio` maintainer.*** [Rust](http://www.rust-lang.org/) bindings for [Python](https://www.python.org/)'s [Asyncio Library](https://docs.python.org/3/library/asyncio.html). This crate facilitates interactions between Rust Futures and Python Coroutines and manages the lifecycle of their corresponding event loops. - PyO3 Project: [Homepage](https://pyo3.rs/) | [GitHub](https://github.com/PyO3/pyo3) - PyO3 Async Runtimes API Documentation: [stable](https://docs.rs/pyo3-async-runtimes/) - Guide for Async / Await [stable](https://pyo3.rs/latest/ecosystem/async-await.html) | [main](https://pyo3.rs/main/ecosystem/async-await.html) - Contributing Notes: [github](https://github.com/PyO3/pyo3-async-runtimes/blob/main/Contributing.md) > PyO3 Asyncio is a _brand new_ part of the broader PyO3 ecosystem. Feel free to open any issues for feature requests or bugfixes for this crate. **If you're a new-comer, the best way to get started is to read through the primer below! For `v0.13` and `v0.14` users I highly recommend reading through the [migration section](#migration-guide) to get a general idea of what's changed in `v0.14` and `v0.15`.** ## Usage Like PyO3, PyO3 Asyncio supports the following software versions: - Python 3.7 and up (CPython and PyPy) - Rust 1.48 and up ## PyO3 Asyncio Primer If you are working with a Python library that makes use of async functions or wish to provide Python bindings for an async Rust library, [`pyo3-async-runtimes`](https://github.com/PyO3/pyo3-async-runtimes) likely has the tools you need. It provides conversions between async functions in both Python and Rust and was designed with first-class support for popular Rust runtimes such as [`tokio`](https://tokio.rs/) and [`async-std`](https://async.rs/). In addition, all async Python code runs on the default `asyncio` event loop, so `pyo3-asyncio` should work just fine with existing Python libraries. In the following sections, we'll give a general overview of `pyo3-asyncio` explaining how to call async Python functions with PyO3, how to call async Rust functions from Python, and how to configure your codebase to manage the runtimes of both. ### Quickstart Here are some examples to get you started right away! A more detailed breakdown of the concepts in these examples can be found in the following sections. #### Rust Applications Here we initialize the runtime, import Python's `asyncio` library and run the given future to completion using Python's default `EventLoop` and `async-std`. Inside the future, we convert `asyncio` sleep into a Rust future and await it. ```toml # Cargo.toml dependencies [dependencies] pyo3 = { version = "0.22" } pyo3-async-runtimes = { version = "0.22", features = ["attributes", "async-std-runtime"] } async-std = "1.13" ``` ```rust //! main.rs use pyo3::prelude::*; #[pyo3_async_runtimes::async_std::main] async fn main() -> PyResult<()> { let fut = Python::with_gil(|py| { let asyncio = py.import_bound("asyncio")?; // convert asyncio.sleep into a Rust Future pyo3_async_runtimes::async_std::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?) })?; fut.await?; Ok(()) } ``` The same application can be written to use `tokio` instead using the `#[pyo3_async_runtimes::tokio::main]` attribute. ```toml # Cargo.toml dependencies [dependencies] pyo3 = { version = "0.22" } pyo3-async-runtimes = { version = "0.22", features = ["attributes", "tokio-runtime"] } tokio = "1.40" ``` ```rust //! main.rs use pyo3::prelude::*; #[pyo3_async_runtimes::tokio::main] async fn main() -> PyResult<()> { let fut = Python::with_gil(|py| { let asyncio = py.import_bound("asyncio")?; // convert asyncio.sleep into a Rust Future pyo3_async_runtimes::tokio::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?) })?; fut.await?; Ok(()) } ``` More details on the usage of this library can be found in the API docs and the primer below. #### PyO3 Native Rust Modules PyO3 Asyncio can also be used to write native modules with async functions. Add the `[lib]` section to `Cargo.toml` to make your library a `cdylib` that Python can import. ```toml [lib] name = "my_async_module" crate-type = ["cdylib"] ``` Make your project depend on `pyo3` with the `extension-module` feature enabled and select your `pyo3-asyncio` runtime: For `async-std`: ```toml [dependencies] pyo3 = { version = "0.22", features = ["extension-module"] } pyo3-async-runtimes = { version = "0.22", features = ["async-std-runtime"] } async-std = "1.13" ``` For `tokio`: ```toml [dependencies] pyo3 = { version = "0.20", features = ["extension-module"] } pyo3-async-runtimes = { version = "0.22", features = ["tokio-runtime"] } tokio = "1.40" ``` Export an async function that makes use of `async-std`: ```rust //! lib.rs use pyo3::{prelude::*, wrap_pyfunction}; #[pyfunction] fn rust_sleep(py: Python) -> PyResult> { pyo3_async_runtimes::async_std::future_into_py(py, async { async_std::task::sleep(std::time::Duration::from_secs(1)).await; Ok(()) }) } #[pymodule] fn my_async_module(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(rust_sleep, m)?)?; Ok(()) } ``` If you want to use `tokio` instead, here's what your module should look like: ```rust //! lib.rs use pyo3::{prelude::*, wrap_pyfunction}; #[pyfunction] fn rust_sleep(py: Python) -> PyResult> { pyo3_async_runtimes::tokio::future_into_py(py, async { tokio::time::sleep(std::time::Duration::from_secs(1)).await; Ok(()) }) } #[pymodule] fn my_async_module(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(rust_sleep, m)?)?; Ok(()) } ``` You can build your module with maturin (see the [Using Rust in Python](https://pyo3.rs/main/#using-rust-from-python) section in the PyO3 guide for setup instructions). After that you should be able to run the Python REPL to try it out. ```bash maturin develop && python3 🔗 Found pyo3 bindings 🐍 Found CPython 3.8 at python3 Finished dev [unoptimized + debuginfo] target(s) in 0.04s Python 3.8.5 (default, Jan 27 2021, 15:41:15) [GCC 9.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import asyncio >>> >>> from my_async_module import rust_sleep >>> >>> async def main(): >>> await rust_sleep() >>> >>> # should sleep for 1s >>> asyncio.run(main()) >>> ``` ### Awaiting an Async Python Function in Rust Let's take a look at a dead simple async Python function: ```python # Sleep for 1 second async def py_sleep(): await asyncio.sleep(1) ``` **Async functions in Python are simply functions that return a `coroutine` object**. For our purposes, we really don't need to know much about these `coroutine` objects. The key factor here is that calling an `async` function is _just like calling a regular function_, the only difference is that we have to do something special with the object that it returns. Normally in Python, that something special is the `await` keyword, but in order to await this coroutine in Rust, we first need to convert it into Rust's version of a `coroutine`: a `Future`. That's where `pyo3-async-runtimes` comes in. [`pyo3_async_runtimes::into_future`](https://docs.rs/pyo3-async-runtimes/latest/pyo3_async_runtimes/fn.into_future.html) performs this conversion for us: ```rust no_run use pyo3::prelude::*; #[pyo3_async_runtimes::tokio::main] async fn main() -> PyResult<()> { let future = Python::with_gil(|py| -> PyResult<_> { // import the module containing the py_sleep function let example = py.import_bound("example")?; // calling the py_sleep method like a normal function // returns a coroutine let coroutine = example.call_method0("py_sleep")?; // convert the coroutine into a Rust future using the // tokio runtime pyo3_async_runtimes::tokio::into_future(coroutine) })?; // await the future future.await?; Ok(()) } ``` > If you're interested in learning more about `coroutines` and `awaitables` in general, check out the > [Python 3 `asyncio` docs](https://docs.python.org/3/library/asyncio-task.html) for more information. ### Awaiting a Rust Future in Python Here we have the same async function as before written in Rust using the [`async-std`](https://async.rs/) runtime: ```rust /// Sleep for 1 second async fn rust_sleep() { async_std::task::sleep(std::time::Duration::from_secs(1)).await; } ``` Similar to Python, Rust's async functions also return a special object called a `Future`: ```rust compile_fail let future = rust_sleep(); ``` We can convert this `Future` object into Python to make it `awaitable`. This tells Python that you can use the `await` keyword with it. In order to do this, we'll call [`pyo3_async_runtimes::async_std::future_into_py`](https://docs.rs/pyo3-asyncio/latest/pyo3_async_runtimes/async_std/fn.future_into_py.html): ```rust use pyo3::prelude::*; async fn rust_sleep() { async_std::task::sleep(std::time::Duration::from_secs(1)).await; } #[pyfunction] fn call_rust_sleep(py: Python) -> PyResult> { pyo3_async_runtimes::async_std::future_into_py(py, async move { rust_sleep().await; Ok(()) }) } ``` In Python, we can call this pyo3 function just like any other async function: ```python from example import call_rust_sleep async def rust_sleep(): await call_rust_sleep() ``` ## Managing Event Loops Python's event loop requires some special treatment, especially regarding the main thread. Some of Python's `asyncio` features, like proper signal handling, require control over the main thread, which doesn't always play well with Rust. Luckily, Rust's event loops are pretty flexible and don't _need_ control over the main thread, so in `pyo3-asyncio`, we decided the best way to handle Rust/Python interop was to just surrender the main thread to Python and run Rust's event loops in the background. Unfortunately, since most event loop implementations _prefer_ control over the main thread, this can still make some things awkward. ### PyO3 Asyncio Initialization Because Python needs to control the main thread, we can't use the convenient proc macros from Rust runtimes to handle the `main` function or `#[test]` functions. Instead, the initialization for PyO3 has to be done from the `main` function and the main thread must block on [`pyo3_async_runtimes::run_forever`](https://docs.rs/pyo3-asyncio/latest/pyo3_async_runtimes/fn.run_forever.html) or [`pyo3_async_runtimes::async_std::run_until_complete`](https://docs.rs/pyo3-asyncio/latest/pyo3_async_runtimes/async_std/fn.run_until_complete.html). Because we have to block on one of those functions, we can't use [`#[async_std::main]`](https://docs.rs/async-std/latest/async_std/attr.main.html) or [`#[tokio::main]`](https://docs.rs/tokio/1.1.0/tokio/attr.main.html) since it's not a good idea to make long blocking calls during an async function. > Internally, these `#[main]` proc macros are expanded to something like this: > > ```rust compile_fail > fn main() { > // your async main fn > async fn _main_impl() { /* ... */ } > Runtime::new().block_on(_main_impl()); > } > ``` > > Making a long blocking call inside the `Future` that's being driven by `block_on` prevents that > thread from doing anything else and can spell trouble for some runtimes (also this will actually > deadlock a single-threaded runtime!). Many runtimes have some sort of `spawn_blocking` mechanism > that can avoid this problem, but again that's not something we can use here since we need it to > block on the _main_ thread. For this reason, `pyo3-asyncio` provides its own set of proc macros to provide you with this initialization. These macros are intended to mirror the initialization of `async-std` and `tokio` while also satisfying the Python runtime's needs. Here's a full example of PyO3 initialization with the `async-std` runtime: ```rust no_run use pyo3::prelude::*; #[pyo3_async_runtimes::async_std::main] async fn main() -> PyResult<()> { // PyO3 is initialized - Ready to go let fut = Python::with_gil(|py| -> PyResult<_> { let asyncio = py.import_bound("asyncio")?; // convert asyncio.sleep into a Rust Future pyo3_async_runtimes::async_std::into_future( asyncio.call_method1("sleep", (1.into_py(py),))? ) })?; fut.await?; Ok(()) } ``` #### A Note About `asyncio.run` In Python 3.7+, the recommended way to run a top-level coroutine with `asyncio` is with `asyncio.run`. In `v0.13` we recommended against using this function due to initialization issues, but in `v0.14` it's perfectly valid to use this function... with a caveat. Since our Rust <--> Python conversions require a reference to the Python event loop, this poses a problem. Imagine we have a PyO3 Asyncio module that defines a `rust_sleep` function like in previous examples. You might rightfully assume that you can call pass this directly into `asyncio.run` like this: ```python import asyncio from my_async_module import rust_sleep asyncio.run(rust_sleep()) ``` You might be surprised to find out that this throws an error: ```bash Traceback (most recent call last): File "example.py", line 5, in asyncio.run(rust_sleep()) RuntimeError: no running event loop ``` What's happening here is that we are calling `rust_sleep` _before_ the future is actually running on the event loop created by `asyncio.run`. This is counter-intuitive, but expected behaviour, and unfortunately there doesn't seem to be a good way of solving this problem within PyO3 Asyncio itself. However, we can make this example work with a simple workaround: ```python import asyncio from my_async_module import rust_sleep # Calling main will just construct the coroutine that later calls rust_sleep. # - This ensures that rust_sleep will be called when the event loop is running, # not before. async def main(): await rust_sleep() # Run the main() coroutine at the top-level instead asyncio.run(main()) ``` #### Non-standard Python Event Loops Python allows you to use alternatives to the default `asyncio` event loop. One popular alternative is `uvloop`. In `v0.13` using non-standard event loops was a bit of an ordeal, but in `v0.14` it's trivial. #### Using `uvloop` in a PyO3 Native Extensions ```toml # Cargo.toml [lib] name = "my_async_module" crate-type = ["cdylib"] [dependencies] pyo3 = { version = "0.22", features = ["extension-module"] } pyo3-async-runtimes = { version = "0.22", features = ["tokio-runtime"] } async-std = "1.13" tokio = "1.40" ``` ```rust //! lib.rs use pyo3::{prelude::*, wrap_pyfunction}; #[pyfunction] fn rust_sleep(py: Python) -> PyResult> { pyo3_async_runtimes::tokio::future_into_py(py, async { tokio::time::sleep(std::time::Duration::from_secs(1)).await; Ok(()) }) } #[pymodule] fn my_async_module(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(rust_sleep, m)?)?; Ok(()) } ``` ```bash $ maturin develop && python3 🔗 Found pyo3 bindings 🐍 Found CPython 3.8 at python3 Finished dev [unoptimized + debuginfo] target(s) in 0.04s Python 3.8.8 (default, Apr 13 2021, 19:58:26) [GCC 7.3.0] :: Anaconda, Inc. on linux Type "help", "copyright", "credits" or "license" for more information. >>> import asyncio >>> import uvloop >>> >>> import my_async_module >>> >>> uvloop.install() >>> >>> async def main(): ... await my_async_module.rust_sleep() ... >>> asyncio.run(main()) >>> ``` #### Using `uvloop` in Rust Applications Using `uvloop` in Rust applications is a bit trickier, but it's still possible with relatively few modifications. Unfortunately, we can't make use of the `#[pyo3_async_runtimes::::main]` attribute with non-standard event loops. This is because the `#[pyo3_async_runtimes::::main]` proc macro has to interact with the Python event loop before we can install the `uvloop` policy. ```toml [dependencies] async-std = "1.13" pyo3 = "0.22" pyo3-async-runtimes = { version = "0.22", features = ["async-std-runtime"] } ``` ```rust no_run //! main.rs use pyo3::{prelude::*, types::PyType}; fn main() -> PyResult<()> { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { let uvloop = py.import_bound("uvloop")?; uvloop.call_method0("install")?; // store a reference for the assertion let uvloop = PyObject::from(uvloop); pyo3_async_runtimes::async_std::run(py, async move { // verify that we are on a uvloop.Loop Python::with_gil(|py| -> PyResult<()> { assert!(uvloop .bind(py) .getattr("Loop")? .downcast::() .unwrap() .is_instance(&pyo3_async_runtimes::async_std::get_current_loop(py)?)?); Ok(()) })?; async_std::task::sleep(std::time::Duration::from_secs(1)).await; Ok(()) }) }) } ``` ### Additional Information - Managing event loop references can be tricky with pyo3-async-runtimes. See [Event Loop References and ContextVars](https://awestlake87.github.io/pyo3-asyncio/master/doc/pyo3_async_runtimes/#event-loop-references-and-contextvars) in the API docs to get a better intuition for how event loop references are managed in this library. - Testing pyo3-asyncio libraries and applications requires a custom test harness since Python requires control over the main thread. You can find a testing guide in the [API docs for the `testing` module](https://awestlake87.github.io/pyo3-asyncio/master/doc/pyo3_async_runtimes/testing) ## Migration Guide ### Migrating from 0.13 to 0.14 So what's changed from `v0.13` to `v0.14`? Well, a lot actually. There were some pretty major flaws in the initialization behaviour of `v0.13`. While it would have been nicer to address these issues without changing the public API, I decided it'd be better to break some of the old API rather than completely change the underlying behaviour of the existing functions. I realize this is going to be a bit of a headache, so hopefully this section will help you through it. To make things a bit easier, I decided to keep most of the old API alongside the new one (with some deprecation warnings to encourage users to move away from it). It should be possible to use the `v0.13` API alongside the newer `v0.14` API, which should allow you to upgrade your application piecemeal rather than all at once. **Before you get started, I personally recommend taking a look at [Event Loop References and ContextVars](https://awestlake87.github.io/pyo3-asyncio/master/doc/pyo3_async_runtimes/#event-loop-references-and-contextvars) in order to get a better grasp on the motivation behind these changes and the nuance involved in using the new conversions.** ### 0.14 Highlights - Tokio initialization is now lazy. - No configuration necessary if you're using the multithreaded scheduler - Calls to `pyo3_async_runtimes::tokio::init_multithread` or `pyo3_async_runtimes::tokio::init_multithread_once` can just be removed. - Calls to `pyo3_async_runtimes::tokio::init_current_thread` or `pyo3_async_runtimes::tokio::init_current_thread_once` require some special attention. - Custom runtime configuration is done by passing a `tokio::runtime::Builder` into `pyo3_async_runtimes::tokio::init` instead of a `tokio::runtime::Runtime` - A new, more correct set of functions has been added to replace the `v0.13` conversions. - `pyo3_async_runtimes::into_future_with_loop` - `pyo3_async_runtimes::::future_into_py_with_loop` - `pyo3_async_runtimes::::local_future_into_py_with_loop` - `pyo3_async_runtimes::::into_future` - `pyo3_async_runtimes::::future_into_py` - `pyo3_async_runtimes::::local_future_into_py` - `pyo3_async_runtimes::::get_current_loop` - `pyo3_async_runtimes::try_init` is no longer required if you're only using `0.14` conversions - The `ThreadPoolExecutor` is no longer configured automatically at the start. - Fortunately, this doesn't seem to have much effect on `v0.13` code, it just means that it's now possible to configure the executor manually as you see fit. ### Upgrading Your Code to 0.14 1. Fix PyO3 0.14 initialization. - PyO3 0.14 feature gated its automatic initialization behaviour behind "auto-initialize". You can either enable the "auto-initialize" behaviour in your project or add a call to `pyo3::prepare_freethreaded_python()` to the start of your program. - If you're using the `#[pyo3_async_runtimes::::main]` proc macro attributes, then you can skip this step. `#[pyo3_async_runtimes::::main]` will call `pyo3::prepare_freethreaded_python()` at the start regardless of your project's "auto-initialize" feature. 2. Fix the tokio initialization. - Calls to `pyo3_async_runtimes::tokio::init_multithread` or `pyo3_async_runtimes::tokio::init_multithread_once` can just be removed. - If you're using the current thread scheduler, you'll need to manually spawn the thread that it runs on during initialization: ```rust no_run let mut builder = tokio::runtime::Builder::new_current_thread(); builder.enable_all(); pyo3_async_runtimes::tokio::init(builder); std::thread::spawn(move || { pyo3_async_runtimes::tokio::get_runtime().block_on( futures::future::pending::<()>() ); }); ``` - Custom `tokio::runtime::Builder` configs can be passed into `pyo3_async_runtimes::tokio::init`. The `tokio::runtime::Runtime` will be lazily instantiated on the first call to `pyo3_async_runtimes::tokio::get_runtime()` 3. If you're using `pyo3_async_runtimes::run_forever` in your application, you should switch to a more manual approach. > `run_forever` is not the recommended way of running an event loop in Python, so it might be a good idea to move away from it. This function would have needed to change for `0.14`, but since it's considered an edge case, it was decided that users could just manually call it if they need to. ```rust use pyo3::prelude::*; fn main() -> PyResult<()> { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { let asyncio = py.import_bound("asyncio")?; let event_loop = asyncio.call_method0("new_event_loop")?; asyncio.call_method1("set_event_loop", (&event_loop,))?; let event_loop_hdl = PyObject::from(event_loop.clone()); pyo3_async_runtimes::tokio::get_runtime().spawn(async move { tokio::time::sleep(std::time::Duration::from_secs(1)).await; // Stop the event loop manually Python::with_gil(|py| { event_loop_hdl .bind(py) .call_method1( "call_soon_threadsafe", (event_loop_hdl .bind(py) .getattr("stop") .unwrap(),), ) .unwrap(); }) }); event_loop.call_method0("run_forever")?; Ok(()) }) } ``` 4. Replace conversions with their newer counterparts. > You may encounter some issues regarding the usage of `get_running_loop` vs `get_event_loop`. For more details on these newer conversions and how they should be used see [Event Loop References and ContextVars](https://awestlake87.github.io/pyo3-asyncio/master/doc/pyo3_async_runtimes/#event-loop-references-and-contextvars). - Replace `pyo3_async_runtimes::into_future` with `pyo3_async_runtimes::::into_future` - Replace `pyo3_async_runtimes::::into_coroutine` with `pyo3_async_runtimes::::future_into_py` - Replace `pyo3_async_runtimes::get_event_loop` with `pyo3_async_runtimes::::get_current_loop` 5. After all conversions have been replaced with their `v0.14` counterparts, `pyo3_async_runtimes::try_init` can safely be removed. > The `v0.13` API has been removed in version `v0.15` ### Migrating from 0.14 to 0.15+ There have been a few changes to the API in order to support proper cancellation from Python and the `contextvars` module. - Any instance of `cancellable_future_into_py` and `local_cancellable_future_into_py` conversions can be replaced with their`future_into_py` and `local_future_into_py` counterparts. > Cancellation support became the default behaviour in 0.15. - Instances of `*_with_loop` conversions should be replaced with the newer `*_with_locals` conversions. ```rust no_run use pyo3::prelude::*; Python::with_gil(|py| -> PyResult<()> { // *_with_loop conversions in 0.14 // // let event_loop = pyo3_async_runtimes::get_running_loop(py)?; // // let fut = pyo3_async_runtimes::tokio::future_into_py_with_loop( // event_loop, // async move { Ok(Python::with_gil(|py| py.None())) } // )?; // // should be replaced with *_with_locals in 0.15+ let fut = pyo3_async_runtimes::tokio::future_into_py_with_locals( py, pyo3_async_runtimes::tokio::get_current_locals(py)?, async move { Ok(()) } )?; Ok(()) }); ``` - `scope` and `scope_local` variants now accept `TaskLocals` instead of `event_loop`. You can usually just replace the `event_loop` with `pyo3_async_runtimes::TaskLocals::new(event_loop).copy_context(py)?`. - Return types for `future_into_py`, `future_into_py_with_locals` `local_future_into_py`, and `local_future_into_py_with_locals` are now constrained by the bound `IntoPy` instead of requiring the return type to be `PyObject`. This can make the return types for futures more flexible, but inference can also fail when the concrete type is ambiguous (for example when using `into()`). Sometimes the `into()` can just be removed, - `run`, and `run_until_complete` can now return any `Send + 'static` value. ### Migrating from 0.15 to 0.16 Actually, not much has changed in the API. I'm happy to say that the PyO3 Asyncio is reaching a pretty stable point in 0.16. For the most part, 0.16 has been about cleanup and removing deprecated functions from the API. PyO3 0.16 comes with a few API changes of its own, but one of the changes that most impacted PyO3 Asyncio was it's decision to drop support for Python 3.6. PyO3 Asyncio has been using a few workarounds / hacks to support the pre-3.7 version of Python's asyncio library that are no longer necessary. PyO3 Asyncio's underlying implementation is now a bit cleaner because of this. PyO3 Asyncio 0.15 included some important fixes to the API in order to add support for proper task cancellation and allow for the preservation / use of contextvars in Python coroutines. This led to the deprecation of some 0.14 functions that were used for edge cases in favor of some more correct versions, and those deprecated functions are now removed from the API in 0.16. In addition, with PyO3 Asyncio 0.16, the library now has experimental support for conversions from Python's async generators into a Rust `Stream`. There are currently two versions `v1` and `v2` with slightly different performance and type signatures, so I'm hoping to get some feedback on which one works best for downstream users. Just enable the `unstable-streams` feature and you're good to go! > The inverse conversion, Rust `Stream` to Python async generator, may come in a later release if > requested! pyo3-async-runtimes-0.22.0/examples/async_std.rs000064400000000000000000000007131046102023000177320ustar 00000000000000use pyo3::prelude::*; #[pyo3_async_runtimes::async_std::main] async fn main() -> PyResult<()> { let fut = Python::with_gil(|py| { let asyncio = py.import_bound("asyncio")?; // convert asyncio.sleep into a Rust Future pyo3_async_runtimes::async_std::into_future( asyncio.call_method1("sleep", (1.into_py(py),))?, ) })?; println!("sleeping for 1s"); fut.await?; println!("done"); Ok(()) } pyo3-async-runtimes-0.22.0/examples/tokio.rs000064400000000000000000000006541046102023000170740ustar 00000000000000use pyo3::prelude::*; #[pyo3_async_runtimes::tokio::main] async fn main() -> PyResult<()> { let fut = Python::with_gil(|py| { let asyncio = py.import_bound("asyncio")?; // convert asyncio.sleep into a Rust Future pyo3_async_runtimes::tokio::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?) })?; println!("sleeping for 1s"); fut.await?; println!("done"); Ok(()) } pyo3-async-runtimes-0.22.0/examples/tokio_current_thread.rs000064400000000000000000000007071046102023000221640ustar 00000000000000use pyo3::prelude::*; #[pyo3_async_runtimes::tokio::main(flavor = "current_thread")] async fn main() -> PyResult<()> { let fut = Python::with_gil(|py| { let asyncio = py.import_bound("asyncio")?; // convert asyncio.sleep into a Rust Future pyo3_async_runtimes::tokio::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?) })?; println!("sleeping for 1s"); fut.await?; println!("done"); Ok(()) } pyo3-async-runtimes-0.22.0/examples/tokio_multi_thread.rs000064400000000000000000000007321046102023000216320ustar 00000000000000use pyo3::prelude::*; #[pyo3_async_runtimes::tokio::main(flavor = "multi_thread", worker_threads = 10)] async fn main() -> PyResult<()> { let fut = Python::with_gil(|py| { let asyncio = py.import_bound("asyncio")?; // convert asyncio.sleep into a Rust Future pyo3_async_runtimes::tokio::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?) })?; println!("sleeping for 1s"); fut.await?; println!("done"); Ok(()) } pyo3-async-runtimes-0.22.0/pytests/common/mod.rs000064400000000000000000000030721046102023000177100ustar 00000000000000use std::{thread, time::Duration}; use pyo3::prelude::*; use pyo3_async_runtimes::TaskLocals; pub(super) const TEST_MOD: &'static str = r#" import asyncio async def py_sleep(duration): await asyncio.sleep(duration) async def sleep_for_1s(sleep_for): await sleep_for(1) "#; pub(super) async fn test_into_future(event_loop: PyObject) -> PyResult<()> { let fut = Python::with_gil(|py| { let test_mod = PyModule::from_code_bound(py, TEST_MOD, "test_rust_coroutine/test_mod.py", "test_mod")?; pyo3_async_runtimes::into_future_with_locals( &TaskLocals::new(event_loop.into_bound(py)), test_mod.call_method1("py_sleep", (1.into_py(py),))?, ) })?; fut.await?; Ok(()) } pub(super) fn test_blocking_sleep() -> PyResult<()> { thread::sleep(Duration::from_secs(1)); Ok(()) } pub(super) async fn test_other_awaitables(event_loop: PyObject) -> PyResult<()> { let fut = Python::with_gil(|py| { let functools = py.import_bound("functools")?; let time = py.import_bound("time")?; // spawn a blocking sleep in the threadpool executor - returns a task, not a coroutine let task = event_loop.bind(py).call_method1( "run_in_executor", ( py.None(), functools.call_method1("partial", (time.getattr("sleep")?, 1))?, ), )?; pyo3_async_runtimes::into_future_with_locals( &TaskLocals::new(event_loop.into_bound(py)), task, ) })?; fut.await?; Ok(()) } pyo3-async-runtimes-0.22.0/pytests/test_async_std_asyncio.rs000064400000000000000000000253201046102023000224140ustar 00000000000000mod common; use std::{ rc::Rc, sync::{Arc, Mutex}, time::Duration, }; use async_std::task; use pyo3::{ prelude::*, types::{IntoPyDict, PyType}, wrap_pyfunction, wrap_pymodule, }; use pyo3_async_runtimes::TaskLocals; #[cfg(feature = "unstable-streams")] use futures::{StreamExt, TryStreamExt}; #[pyfunction] fn sleep<'p>(py: Python<'p>, secs: Bound<'p, PyAny>) -> PyResult> { let secs = secs.extract()?; pyo3_async_runtimes::async_std::future_into_py(py, async move { task::sleep(Duration::from_secs(secs)).await; Ok(()) }) } #[pyo3_async_runtimes::async_std::test] async fn test_future_into_py() -> PyResult<()> { let fut = Python::with_gil(|py| { let sleeper_mod = PyModule::new_bound(py, "rust_sleeper")?; sleeper_mod.add_wrapped(wrap_pyfunction!(sleep))?; let test_mod = PyModule::from_code_bound( py, common::TEST_MOD, "test_future_into_py_mod.py", "test_future_into_py_mod", )?; pyo3_async_runtimes::async_std::into_future( test_mod.call_method1("sleep_for_1s", (sleeper_mod.getattr("sleep")?,))?, ) })?; fut.await?; Ok(()) } #[pyo3_async_runtimes::async_std::test] async fn test_async_sleep() -> PyResult<()> { let asyncio = Python::with_gil(|py| { py.import_bound("asyncio") .map(|asyncio| PyObject::from(asyncio)) })?; task::sleep(Duration::from_secs(1)).await; Python::with_gil(|py| { pyo3_async_runtimes::async_std::into_future(asyncio.bind(py).call_method1("sleep", (1.0,))?) })? .await?; Ok(()) } #[pyo3_async_runtimes::async_std::test] fn test_blocking_sleep() -> PyResult<()> { common::test_blocking_sleep() } #[pyo3_async_runtimes::async_std::test] async fn test_into_future() -> PyResult<()> { common::test_into_future(Python::with_gil(|py| { pyo3_async_runtimes::async_std::get_current_loop(py) .unwrap() .into() })) .await } #[pyo3_async_runtimes::async_std::test] async fn test_other_awaitables() -> PyResult<()> { common::test_other_awaitables(Python::with_gil(|py| { pyo3_async_runtimes::async_std::get_current_loop(py) .unwrap() .into() })) .await } #[pyo3_async_runtimes::async_std::test] async fn test_panic() -> PyResult<()> { let fut = Python::with_gil(|py| -> PyResult<_> { pyo3_async_runtimes::async_std::into_future( pyo3_async_runtimes::async_std::future_into_py::<_, ()>(py, async { panic!("this panic was intentional!") })?, ) })?; match fut.await { Ok(_) => panic!("coroutine should panic"), Err(e) => Python::with_gil(|py| { if e.is_instance_of::(py) { Ok(()) } else { panic!("expected RustPanic err") } }), } } #[pyo3_async_runtimes::async_std::test] async fn test_local_future_into_py() -> PyResult<()> { Python::with_gil(|py| { let non_send_secs = Rc::new(1); #[allow(deprecated)] let py_future = pyo3_async_runtimes::async_std::local_future_into_py(py, async move { async_std::task::sleep(Duration::from_secs(*non_send_secs)).await; Ok(()) })?; pyo3_async_runtimes::async_std::into_future(py_future) })? .await?; Ok(()) } #[pyo3_async_runtimes::async_std::test] async fn test_cancel() -> PyResult<()> { let completed = Arc::new(Mutex::new(false)); let py_future = Python::with_gil(|py| -> PyResult { let completed = Arc::clone(&completed); Ok( pyo3_async_runtimes::async_std::future_into_py(py, async move { async_std::task::sleep(Duration::from_secs(1)).await; *completed.lock().unwrap() = true; Ok(()) })? .into(), ) })?; if let Err(e) = Python::with_gil(|py| -> PyResult<_> { py_future.bind(py).call_method0("cancel")?; pyo3_async_runtimes::async_std::into_future(py_future.into_bound(py)) })? .await { Python::with_gil(|py| -> PyResult<()> { assert!(e.value_bound(py).is_instance( py.import_bound("asyncio")? .getattr("CancelledError")? .downcast::() .unwrap() )?); Ok(()) })?; } else { panic!("expected CancelledError"); } async_std::task::sleep(Duration::from_secs(1)).await; if *completed.lock().unwrap() { panic!("future still completed") } Ok(()) } #[cfg(feature = "unstable-streams")] const ASYNC_STD_TEST_MOD: &str = r#" import asyncio async def gen(): for i in range(10): await asyncio.sleep(0.1) yield i "#; #[cfg(feature = "unstable-streams")] #[pyo3_async_runtimes::async_std::test] async fn test_async_gen_v1() -> PyResult<()> { let stream = Python::with_gil(|py| { let test_mod = PyModule::from_code_bound( py, ASYNC_STD_TEST_MOD, "test_rust_coroutine/async_std_test_mod.py", "async_std_test_mod", )?; pyo3_async_runtimes::async_std::into_stream_v1(test_mod.call_method0("gen")?) })?; let vals = stream .map(|item| Python::with_gil(|py| -> PyResult { Ok(item?.bind(py).extract()?) })) .try_collect::>() .await?; assert_eq!((0..10).collect::>(), vals); Ok(()) } #[pyo3_async_runtimes::async_std::test] fn test_local_cancel(event_loop: PyObject) -> PyResult<()> { let locals = Python::with_gil(|py| -> PyResult { Ok(TaskLocals::new(event_loop.into_bound(py)).copy_context(py)?) })?; async_std::task::block_on(pyo3_async_runtimes::async_std::scope_local(locals, async { let completed = Arc::new(Mutex::new(false)); let py_future = Python::with_gil(|py| -> PyResult { let completed = Arc::clone(&completed); Ok( pyo3_async_runtimes::async_std::future_into_py(py, async move { async_std::task::sleep(Duration::from_secs(1)).await; *completed.lock().unwrap() = true; Ok(()) })? .into(), ) })?; if let Err(e) = Python::with_gil(|py| -> PyResult<_> { py_future.bind(py).call_method0("cancel")?; pyo3_async_runtimes::async_std::into_future(py_future.into_bound(py)) })? .await { Python::with_gil(|py| -> PyResult<()> { assert!(e.value_bound(py).is_instance( py.import_bound("asyncio")? .getattr("CancelledError")? .downcast::() .unwrap() )?); Ok(()) })?; } else { panic!("expected CancelledError"); } async_std::task::sleep(Duration::from_secs(1)).await; if *completed.lock().unwrap() { panic!("future still completed") } Ok(()) })) } /// This module is implemented in Rust. #[pymodule] fn test_mod(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { #![allow(deprecated)] #[pyfunction(name = "sleep")] fn sleep_(py: Python) -> PyResult> { pyo3_async_runtimes::async_std::future_into_py(py, async move { async_std::task::sleep(Duration::from_millis(500)).await; Ok(()) }) } m.add_function(wrap_pyfunction!(sleep_, m)?)?; Ok(()) } const MULTI_ASYNCIO_CODE: &str = r#" async def main(): return await test_mod.sleep() asyncio.new_event_loop().run_until_complete(main()) "#; #[pyo3_async_runtimes::async_std::test] fn test_multiple_asyncio_run() -> PyResult<()> { Python::with_gil(|py| { pyo3_async_runtimes::async_std::run(py, async move { async_std::task::sleep(Duration::from_millis(500)).await; Ok(()) })?; pyo3_async_runtimes::async_std::run(py, async move { async_std::task::sleep(Duration::from_millis(500)).await; Ok(()) })?; let d = [ ("asyncio", py.import_bound("asyncio")?.into()), ("test_mod", wrap_pymodule!(test_mod)(py)), ] .into_py_dict_bound(py); py.run_bound(MULTI_ASYNCIO_CODE, Some(&d), None)?; py.run_bound(MULTI_ASYNCIO_CODE, Some(&d), None)?; Ok(()) }) } #[pymodule] fn cvars_mod(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { #![allow(deprecated)] #[pyfunction] pub(crate) fn async_callback(py: Python, callback: PyObject) -> PyResult> { pyo3_async_runtimes::async_std::future_into_py(py, async move { Python::with_gil(|py| { pyo3_async_runtimes::async_std::into_future(callback.bind(py).call0()?) })? .await?; Ok(()) }) } m.add_function(wrap_pyfunction!(async_callback, m)?)?; Ok(()) } #[cfg(feature = "unstable-streams")] #[pyo3_async_runtimes::async_std::test] async fn test_async_gen_v2() -> PyResult<()> { let stream = Python::with_gil(|py| { let test_mod = PyModule::from_code_bound( py, ASYNC_STD_TEST_MOD, "test_rust_coroutine/async_std_test_mod.py", "async_std_test_mod", )?; pyo3_async_runtimes::async_std::into_stream_v2(test_mod.call_method0("gen")?) })?; let vals = stream .map(|item| Python::with_gil(|py| -> PyResult { Ok(item.bind(py).extract()?) })) .try_collect::>() .await?; assert_eq!((0..10).collect::>(), vals); Ok(()) } const CONTEXTVARS_CODE: &str = r#" cx = contextvars.ContextVar("cx") async def contextvars_test(): assert cx.get() == "foobar" async def main(): cx.set("foobar") await cvars_mod.async_callback(contextvars_test) asyncio.run(main()) "#; #[pyo3_async_runtimes::async_std::test] fn test_contextvars() -> PyResult<()> { Python::with_gil(|py| { let d = [ ("asyncio", py.import_bound("asyncio")?.into()), ("contextvars", py.import_bound("contextvars")?.into()), ("cvars_mod", wrap_pymodule!(cvars_mod)(py)), ] .into_py_dict_bound(py); py.run_bound(CONTEXTVARS_CODE, Some(&d), None)?; py.run_bound(CONTEXTVARS_CODE, Some(&d), None)?; Ok(()) }) } fn main() -> pyo3::PyResult<()> { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { pyo3_async_runtimes::async_std::run(py, pyo3_async_runtimes::testing::main()) }) } pyo3-async-runtimes-0.22.0/pytests/test_async_std_run_forever.rs000064400000000000000000000026061046102023000233050ustar 00000000000000use std::time::Duration; use pyo3::prelude::*; fn dump_err(py: Python, e: PyErr) { // We can't display Python exceptions via std::fmt::Display, // so print the error here manually. e.print_and_set_sys_last_vars(py); } fn main() { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { let asyncio = py.import_bound("asyncio")?; let event_loop = asyncio.call_method0("new_event_loop")?; asyncio.call_method1("set_event_loop", (&event_loop,))?; let event_loop_hdl = PyObject::from(event_loop.clone()); async_std::task::spawn(async move { async_std::task::sleep(Duration::from_secs(1)).await; Python::with_gil(|py| { event_loop_hdl .bind(py) .call_method1( "call_soon_threadsafe", (event_loop_hdl .bind(py) .getattr("stop") .map_err(|e| dump_err(py, e)) .unwrap(),), ) .map_err(|e| dump_err(py, e)) .unwrap(); }) }); event_loop.call_method0("run_forever")?; println!("test test_async_std_run_forever ... ok"); Ok(()) }) .map_err(|e| Python::with_gil(|py| dump_err(py, e))) .unwrap() } pyo3-async-runtimes-0.22.0/pytests/test_async_std_uvloop.rs000064400000000000000000000021741046102023000222750ustar 00000000000000#[cfg(not(target_os = "windows"))] fn main() -> pyo3::PyResult<()> { use pyo3::{prelude::*, types::PyType}; pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { let uvloop = py.import_bound("uvloop")?; uvloop.call_method0("install")?; // store a reference for the assertion let uvloop = PyObject::from(uvloop); pyo3_async_runtimes::async_std::run(py, async move { // verify that we are on a uvloop.Loop Python::with_gil(|py| -> PyResult<()> { assert!( pyo3_async_runtimes::async_std::get_current_loop(py)?.is_instance( uvloop .bind(py) .getattr("Loop")? .downcast::() .unwrap() )? ); Ok(()) })?; async_std::task::sleep(std::time::Duration::from_secs(1)).await; println!("test test_async_std_uvloop ... ok"); Ok(()) }) }) } #[cfg(target_os = "windows")] fn main() {} pyo3-async-runtimes-0.22.0/pytests/test_race_condition_regression.rs000064400000000000000000000037161046102023000241250ustar 00000000000000use pyo3::{prelude::*, wrap_pyfunction}; #[pyfunction] fn sleep<'p>(py: Python<'p>, secs: Bound<'p, PyAny>) -> PyResult> { let secs = secs.extract()?; pyo3_async_runtimes::async_std::future_into_py(py, async move { async_std::task::sleep(std::time::Duration::from_secs_f64(secs)).await; Ok(()) }) } const RACE_CONDITION_REGRESSION_TEST: &str = r#" import asyncio import random async def trigger_race_condition(rust_sleeper, delay): coro = asyncio.wrap_future(rust_sleeper(0.1)) await asyncio.sleep(delay) coro.cancel() def main(rust_sleeper): race_condition_triggered = False for i in range(1000): delay = random.uniform(0.099, 0.101) loop = asyncio.new_event_loop() loop.set_debug(True) def custom_exception_handler(loop, context): nonlocal race_condition_triggered race_condition_triggered = True try: loop.set_exception_handler(custom_exception_handler) loop.run_until_complete(trigger_race_condition(rust_sleeper, delay)) if race_condition_triggered: raise Exception("Race condition triggered") finally: loop.run_until_complete(loop.shutdown_asyncgens()) if hasattr(loop, 'shutdown_default_executor'): loop.run_until_complete(loop.shutdown_default_executor()) loop.close() "#; fn main() -> pyo3::PyResult<()> { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| -> PyResult<()> { let sleeper_mod = PyModule::new_bound(py, "rust_sleeper")?; sleeper_mod.add_wrapped(wrap_pyfunction!(sleep))?; let test_mod = PyModule::from_code_bound( py, RACE_CONDITION_REGRESSION_TEST, "race_condition_regression_test.py", "race_condition_regression_test", )?; test_mod.call_method1("main", (sleeper_mod.getattr("sleep")?,))?; Ok(()) }) } pyo3-async-runtimes-0.22.0/pytests/test_tokio_current_thread_asyncio.rs000064400000000000000000000010511046102023000246360ustar 00000000000000mod common; mod tokio_asyncio; use pyo3::prelude::*; fn main() -> pyo3::PyResult<()> { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { let mut builder = tokio::runtime::Builder::new_current_thread(); builder.enable_all(); pyo3_async_runtimes::tokio::init(builder); std::thread::spawn(move || { pyo3_async_runtimes::tokio::get_runtime().block_on(futures::future::pending::<()>()); }); pyo3_async_runtimes::tokio::run(py, pyo3_async_runtimes::testing::main()) }) } pyo3-async-runtimes-0.22.0/pytests/test_tokio_current_thread_run_forever.rs000064400000000000000000000007161046102023000255340ustar 00000000000000mod tokio_run_forever; fn main() { pyo3::prepare_freethreaded_python(); let mut builder = tokio::runtime::Builder::new_current_thread(); builder.enable_all(); pyo3_async_runtimes::tokio::init(builder); std::thread::spawn(move || { pyo3_async_runtimes::tokio::get_runtime().block_on(futures::future::pending::<()>()); }); tokio_run_forever::test_main(); println!("test test_tokio_current_thread_run_forever ... ok"); } pyo3-async-runtimes-0.22.0/pytests/test_tokio_current_thread_uvloop.rs000064400000000000000000000026231046102023000245230ustar 00000000000000#[cfg(not(target_os = "windows"))] fn main() -> pyo3::PyResult<()> { use pyo3::{prelude::*, types::PyType}; pyo3::prepare_freethreaded_python(); let mut builder = tokio::runtime::Builder::new_current_thread(); builder.enable_all(); pyo3_async_runtimes::tokio::init(builder); std::thread::spawn(move || { pyo3_async_runtimes::tokio::get_runtime().block_on(futures::future::pending::<()>()); }); Python::with_gil(|py| { let uvloop = py.import_bound("uvloop")?; uvloop.call_method0("install")?; // store a reference for the assertion let uvloop = PyObject::from(uvloop); pyo3_async_runtimes::tokio::run(py, async move { // verify that we are on a uvloop.Loop Python::with_gil(|py| -> PyResult<()> { assert!( pyo3_async_runtimes::tokio::get_current_loop(py)?.is_instance( uvloop .bind(py) .getattr("Loop")? .downcast::() .unwrap() )? ); Ok(()) })?; tokio::time::sleep(std::time::Duration::from_secs(1)).await; println!("test test_tokio_current_thread_uvloop ... ok"); Ok(()) }) }) } #[cfg(target_os = "windows")] fn main() {} pyo3-async-runtimes-0.22.0/pytests/test_tokio_multi_thread_asyncio.rs000064400000000000000000000003521046102023000243110ustar 00000000000000mod common; mod tokio_asyncio; use pyo3::prelude::*; fn main() -> pyo3::PyResult<()> { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| pyo3_async_runtimes::tokio::run(py, pyo3_async_runtimes::testing::main())) } pyo3-async-runtimes-0.22.0/pytests/test_tokio_multi_thread_run_forever.rs000064400000000000000000000002641046102023000252020ustar 00000000000000mod tokio_run_forever; fn main() { pyo3::prepare_freethreaded_python(); tokio_run_forever::test_main(); println!("test test_tokio_multi_thread_run_forever ... ok"); } pyo3-async-runtimes-0.22.0/pytests/test_tokio_multi_thread_uvloop.rs000064400000000000000000000021721046102023000241720ustar 00000000000000#[cfg(not(target_os = "windows"))] fn main() -> pyo3::PyResult<()> { use pyo3::{prelude::*, types::PyType}; pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { let uvloop = py.import_bound("uvloop")?; uvloop.call_method0("install")?; // store a reference for the assertion let uvloop = PyObject::from(uvloop); pyo3_async_runtimes::tokio::run(py, async move { // verify that we are on a uvloop.Loop Python::with_gil(|py| -> PyResult<()> { assert!( pyo3_async_runtimes::tokio::get_current_loop(py)?.is_instance( uvloop .bind(py) .getattr("Loop")? .downcast::() .unwrap() )? ); Ok(()) })?; tokio::time::sleep(std::time::Duration::from_secs(1)).await; println!("test test_tokio_multi_thread_uvloop ... ok"); Ok(()) }) }) } #[cfg(target_os = "windows")] fn main() {} pyo3-async-runtimes-0.22.0/pytests/tokio_asyncio/mod.rs000064400000000000000000000256261046102023000213030ustar 00000000000000use std::{ rc::Rc, sync::{Arc, Mutex}, time::Duration, }; use pyo3::{ prelude::*, types::{IntoPyDict, PyType}, wrap_pyfunction, wrap_pymodule, }; use pyo3_async_runtimes::TaskLocals; #[cfg(feature = "unstable-streams")] use futures::{StreamExt, TryStreamExt}; use crate::common; #[pyfunction] fn sleep<'p>(py: Python<'p>, secs: Bound<'p, PyAny>) -> PyResult> { let secs = secs.extract()?; pyo3_async_runtimes::tokio::future_into_py(py, async move { tokio::time::sleep(Duration::from_secs(secs)).await; Ok(()) }) } #[pyo3_async_runtimes::tokio::test] async fn test_future_into_py() -> PyResult<()> { let fut = Python::with_gil(|py| { let sleeper_mod = PyModule::new_bound(py, "rust_sleeper")?; sleeper_mod.add_wrapped(wrap_pyfunction!(sleep))?; let test_mod = PyModule::from_code_bound( py, common::TEST_MOD, "test_future_into_py_mod.py", "test_future_into_py_mod", )?; pyo3_async_runtimes::tokio::into_future( test_mod.call_method1("sleep_for_1s", (sleeper_mod.getattr("sleep")?,))?, ) })?; fut.await?; Ok(()) } #[pyo3_async_runtimes::tokio::test] async fn test_async_sleep() -> PyResult<()> { let asyncio = Python::with_gil(|py| { py.import_bound("asyncio") .map(|asyncio| PyObject::from(asyncio)) })?; tokio::time::sleep(Duration::from_secs(1)).await; Python::with_gil(|py| { pyo3_async_runtimes::tokio::into_future(asyncio.bind(py).call_method1("sleep", (1.0,))?) })? .await?; Ok(()) } #[pyo3_async_runtimes::tokio::test] fn test_blocking_sleep() -> PyResult<()> { common::test_blocking_sleep() } #[pyo3_async_runtimes::tokio::test] async fn test_into_future() -> PyResult<()> { common::test_into_future(Python::with_gil(|py| { pyo3_async_runtimes::tokio::get_current_loop(py) .unwrap() .into() })) .await } #[pyo3_async_runtimes::tokio::test] async fn test_other_awaitables() -> PyResult<()> { common::test_other_awaitables(Python::with_gil(|py| { pyo3_async_runtimes::tokio::get_current_loop(py) .unwrap() .into() })) .await } #[pyo3_async_runtimes::tokio::test] fn test_local_future_into_py(event_loop: PyObject) -> PyResult<()> { tokio::task::LocalSet::new().block_on(pyo3_async_runtimes::tokio::get_runtime(), async { Python::with_gil(|py| { let non_send_secs = Rc::new(1); #[allow(deprecated)] let py_future = pyo3_async_runtimes::tokio::local_future_into_py_with_locals( py, TaskLocals::new(event_loop.bind(py).clone()), async move { tokio::time::sleep(Duration::from_secs(*non_send_secs)).await; Ok(()) }, )?; pyo3_async_runtimes::into_future_with_locals( &TaskLocals::new(event_loop.into_bound(py)), py_future, ) })? .await?; Ok(()) }) } #[pyo3_async_runtimes::tokio::test] async fn test_panic() -> PyResult<()> { let fut = Python::with_gil(|py| -> PyResult<_> { pyo3_async_runtimes::tokio::into_future( pyo3_async_runtimes::tokio::future_into_py::<_, ()>(py, async { panic!("this panic was intentional!") })?, ) })?; match fut.await { Ok(_) => panic!("coroutine should panic"), Err(e) => Python::with_gil(|py| { if e.is_instance_of::(py) { Ok(()) } else { panic!("expected RustPanic err") } }), } } #[pyo3_async_runtimes::tokio::test] async fn test_cancel() -> PyResult<()> { let completed = Arc::new(Mutex::new(false)); let py_future = Python::with_gil(|py| -> PyResult { let completed = Arc::clone(&completed); Ok(pyo3_async_runtimes::tokio::future_into_py(py, async move { tokio::time::sleep(Duration::from_secs(1)).await; *completed.lock().unwrap() = true; Ok(()) })? .into()) })?; if let Err(e) = Python::with_gil(|py| -> PyResult<_> { py_future.bind(py).call_method0("cancel")?; pyo3_async_runtimes::tokio::into_future(py_future.into_bound(py)) })? .await { Python::with_gil(|py| -> PyResult<()> { assert!(e.value_bound(py).is_instance( py.import_bound("asyncio")? .getattr("CancelledError")? .downcast::() .unwrap() )?); Ok(()) })?; } else { panic!("expected CancelledError"); } tokio::time::sleep(Duration::from_secs(1)).await; if *completed.lock().unwrap() { panic!("future still completed") } Ok(()) } #[pyo3_async_runtimes::tokio::test] #[allow(deprecated)] fn test_local_cancel(event_loop: PyObject) -> PyResult<()> { let locals = Python::with_gil(|py| -> PyResult { Ok(TaskLocals::new(event_loop.into_bound(py)).copy_context(py)?) })?; tokio::task::LocalSet::new().block_on( pyo3_async_runtimes::tokio::get_runtime(), pyo3_async_runtimes::tokio::scope_local(locals, async { let completed = Arc::new(Mutex::new(false)); let py_future = Python::with_gil(|py| -> PyResult { let completed = Arc::clone(&completed); #[allow(deprecated)] Ok( pyo3_async_runtimes::tokio::local_future_into_py(py, async move { tokio::time::sleep(Duration::from_secs(1)).await; *completed.lock().unwrap() = true; Ok(()) })? .into(), ) })?; if let Err(e) = Python::with_gil(|py| -> PyResult<_> { py_future.bind(py).call_method0("cancel")?; pyo3_async_runtimes::tokio::into_future(py_future.into_bound(py)) })? .await { Python::with_gil(|py| -> PyResult<()> { assert!(e.value_bound(py).is_instance( py.import_bound("asyncio")? .getattr("CancelledError")? .downcast::() .unwrap() )?); Ok(()) })?; } else { panic!("expected CancelledError"); } tokio::time::sleep(Duration::from_secs(1)).await; if *completed.lock().unwrap() { panic!("future still completed") } Ok(()) }), ) } /// This module is implemented in Rust. #[pymodule] fn test_mod(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { #![allow(deprecated)] #[pyfunction(name = "sleep")] fn sleep_(py: Python) -> PyResult> { pyo3_async_runtimes::tokio::future_into_py(py, async move { tokio::time::sleep(Duration::from_millis(500)).await; Ok(()) }) } m.add_function(wrap_pyfunction!(sleep_, m)?)?; Ok(()) } const TEST_CODE: &str = r#" async def main(): return await test_mod.sleep() asyncio.new_event_loop().run_until_complete(main()) "#; #[pyo3_async_runtimes::tokio::test] fn test_multiple_asyncio_run() -> PyResult<()> { Python::with_gil(|py| { pyo3_async_runtimes::tokio::run(py, async move { tokio::time::sleep(Duration::from_millis(500)).await; Ok(()) })?; pyo3_async_runtimes::tokio::run(py, async move { tokio::time::sleep(Duration::from_millis(500)).await; Ok(()) })?; let d = [ ("asyncio", py.import_bound("asyncio")?.into()), ("test_mod", wrap_pymodule!(test_mod)(py)), ] .into_py_dict_bound(py); py.run_bound(TEST_CODE, Some(&d), None)?; py.run_bound(TEST_CODE, Some(&d), None)?; Ok(()) }) } #[pymodule] fn cvars_mod(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { #![allow(deprecated)] #[pyfunction] fn async_callback(py: Python, callback: PyObject) -> PyResult> { pyo3_async_runtimes::tokio::future_into_py(py, async move { Python::with_gil(|py| { pyo3_async_runtimes::tokio::into_future(callback.bind(py).call0()?) })? .await?; Ok(()) }) } m.add_function(wrap_pyfunction!(async_callback, m)?)?; Ok(()) } #[cfg(feature = "unstable-streams")] const TOKIO_TEST_MOD: &str = r#" import asyncio async def gen(): for i in range(10): await asyncio.sleep(0.1) yield i "#; #[cfg(feature = "unstable-streams")] #[pyo3_async_runtimes::tokio::test] async fn test_async_gen_v1() -> PyResult<()> { let stream = Python::with_gil(|py| { let test_mod = PyModule::from_code_bound( py, TOKIO_TEST_MOD, "test_rust_coroutine/tokio_test_mod.py", "tokio_test_mod", )?; pyo3_async_runtimes::tokio::into_stream_v1(test_mod.call_method0("gen")?) })?; let vals = stream .map(|item| Python::with_gil(|py| -> PyResult { Ok(item?.bind(py).extract()?) })) .try_collect::>() .await?; assert_eq!((0..10).collect::>(), vals); Ok(()) } #[cfg(feature = "unstable-streams")] #[pyo3_async_runtimes::tokio::test] async fn test_async_gen_v2() -> PyResult<()> { let stream = Python::with_gil(|py| { let test_mod = PyModule::from_code_bound( py, TOKIO_TEST_MOD, "test_rust_coroutine/tokio_test_mod.py", "tokio_test_mod", )?; pyo3_async_runtimes::tokio::into_stream_v2(test_mod.call_method0("gen")?) })?; let vals = stream .map(|item| Python::with_gil(|py| -> PyResult { Ok(item.bind(py).extract()?) })) .try_collect::>() .await?; assert_eq!((0..10).collect::>(), vals); Ok(()) } const CONTEXTVARS_CODE: &str = r#" cx = contextvars.ContextVar("cx") async def contextvars_test(): assert cx.get() == "foobar" async def main(): cx.set("foobar") await cvars_mod.async_callback(contextvars_test) asyncio.run(main()) "#; #[pyo3_async_runtimes::tokio::test] fn test_contextvars() -> PyResult<()> { Python::with_gil(|py| { let d = [ ("asyncio", py.import_bound("asyncio")?.into()), ("contextvars", py.import_bound("contextvars")?.into()), ("cvars_mod", wrap_pymodule!(cvars_mod)(py)), ] .into_py_dict_bound(py); py.run_bound(CONTEXTVARS_CODE, Some(&d), None)?; py.run_bound(CONTEXTVARS_CODE, Some(&d), None)?; Ok(()) }) } pyo3-async-runtimes-0.22.0/pytests/tokio_run_forever/mod.rs000064400000000000000000000025121046102023000221570ustar 00000000000000use std::time::Duration; use pyo3::prelude::*; fn dump_err(py: Python<'_>, e: PyErr) { // We can't display Python exceptions via std::fmt::Display, // so print the error here manually. e.print_and_set_sys_last_vars(py); } pub(super) fn test_main() { Python::with_gil(|py| { let asyncio = py.import_bound("asyncio")?; let event_loop = asyncio.call_method0("new_event_loop")?; asyncio.call_method1("set_event_loop", (&event_loop,))?; let event_loop_hdl = PyObject::from(event_loop.clone()); pyo3_async_runtimes::tokio::get_runtime().spawn(async move { tokio::time::sleep(Duration::from_secs(1)).await; Python::with_gil(|py| { event_loop_hdl .bind(py) .call_method1( "call_soon_threadsafe", (event_loop_hdl .bind(py) .getattr("stop") .map_err(|e| dump_err(py, e)) .unwrap(),), ) .map_err(|e| dump_err(py, e)) .unwrap(); }) }); event_loop.call_method0("run_forever")?; Ok(()) }) .map_err(|e| Python::with_gil(|py| dump_err(py, e))) .unwrap(); } pyo3-async-runtimes-0.22.0/src/async_std.rs000064400000000000000000000635011046102023000167070ustar 00000000000000//! async-std-runtime PyO3 Asyncio functions specific to the async-std runtime //! //! Items marked with //! unstable-streams //! are only available when the `unstable-streams` Cargo feature is enabled: //! //! ```toml //! [dependencies.pyo3-async-runtimes] //! version = "0.22" //! features = ["unstable-streams"] //! ``` use async_std::task; use futures::FutureExt; use pyo3::prelude::*; use std::{any::Any, cell::RefCell, future::Future, panic::AssertUnwindSafe, pin::Pin}; use crate::{ generic::{self, ContextExt, JoinError, LocalContextExt, Runtime, SpawnLocalExt}, TaskLocals, }; /// attributes /// re-exports for macros #[cfg(feature = "attributes")] pub mod re_exports { /// re-export spawn_blocking for use in `#[test]` macro without external dependency pub use async_std::task::spawn_blocking; } /// attributes Provides the boilerplate for the `async-std` runtime and runs an async fn as main #[cfg(feature = "attributes")] pub use pyo3_async_runtimes_macros::async_std_main as main; /// attributes /// testing /// Registers an `async-std` test with the `pyo3-asyncio` test harness #[cfg(all(feature = "attributes", feature = "testing"))] pub use pyo3_async_runtimes_macros::async_std_test as test; struct AsyncStdJoinErr(Box); impl JoinError for AsyncStdJoinErr { fn is_panic(&self) -> bool { true } fn into_panic(self) -> Box { self.0 } } async_std::task_local! { static TASK_LOCALS: RefCell> = RefCell::new(None); } struct AsyncStdRuntime; impl Runtime for AsyncStdRuntime { type JoinError = AsyncStdJoinErr; type JoinHandle = task::JoinHandle>; fn spawn(fut: F) -> Self::JoinHandle where F: Future + Send + 'static, { task::spawn(async move { AssertUnwindSafe(fut) .catch_unwind() .await .map_err(AsyncStdJoinErr) }) } } impl ContextExt for AsyncStdRuntime { fn scope(locals: TaskLocals, fut: F) -> Pin + Send>> where F: Future + Send + 'static, { let old = TASK_LOCALS.with(|c| c.replace(Some(locals))); Box::pin(async move { let result = fut.await; TASK_LOCALS.with(|c| c.replace(old)); result }) } fn get_task_locals() -> Option { TASK_LOCALS .try_with(|c| { c.borrow() .as_ref() .map(|locals| Python::with_gil(|py| locals.clone_ref(py))) }) .unwrap_or_default() } } impl SpawnLocalExt for AsyncStdRuntime { fn spawn_local(fut: F) -> Self::JoinHandle where F: Future + 'static, { task::spawn_local(async move { fut.await; Ok(()) }) } } impl LocalContextExt for AsyncStdRuntime { fn scope_local(locals: TaskLocals, fut: F) -> Pin>> where F: Future + 'static, { let old = TASK_LOCALS.with(|c| c.replace(Some(locals))); Box::pin(async move { let result = fut.await; TASK_LOCALS.with(|c| c.replace(old)); result }) } } /// Set the task local event loop for the given future pub async fn scope(locals: TaskLocals, fut: F) -> R where F: Future + Send + 'static, { AsyncStdRuntime::scope(locals, fut).await } /// Set the task local event loop for the given !Send future pub async fn scope_local(locals: TaskLocals, fut: F) -> R where F: Future + 'static, { AsyncStdRuntime::scope_local(locals, fut).await } /// Get the current event loop from either Python or Rust async task local context /// /// This function first checks if the runtime has a task-local reference to the Python event loop. /// If not, it calls [`get_running_loop`](`crate::get_running_loop`) to get the event loop /// associated with the current OS thread. pub fn get_current_loop(py: Python) -> PyResult> { generic::get_current_loop::(py) } /// Either copy the task locals from the current task OR get the current running loop and /// contextvars from Python. pub fn get_current_locals(py: Python) -> PyResult { generic::get_current_locals::(py) } /// Run the event loop until the given Future completes /// /// The event loop runs until the given future is complete. /// /// After this function returns, the event loop can be resumed with [`run_until_complete`] /// /// # Arguments /// * `event_loop` - The Python event loop that should run the future /// * `fut` - The future to drive to completion /// /// # Examples /// /// ``` /// # use std::time::Duration; /// # /// # use pyo3::prelude::*; /// # /// # pyo3::prepare_freethreaded_python(); /// # /// # Python::with_gil(|py| -> PyResult<()> { /// # let event_loop = py.import_bound("asyncio")?.call_method0("new_event_loop")?; /// pyo3_async_runtimes::async_std::run_until_complete(event_loop, async move { /// async_std::task::sleep(Duration::from_secs(1)).await; /// Ok(()) /// })?; /// # Ok(()) /// # }).unwrap(); /// ``` pub fn run_until_complete(event_loop: Bound, fut: F) -> PyResult where F: Future> + Send + 'static, T: Send + Sync + 'static, { generic::run_until_complete::(&event_loop, fut) } /// Run the event loop until the given Future completes /// /// # Arguments /// * `py` - The current PyO3 GIL guard /// * `fut` - The future to drive to completion /// /// # Examples /// /// ```no_run /// # use std::time::Duration; /// # /// # use pyo3::prelude::*; /// # /// fn main() { /// // call this or use pyo3 0.14 "auto-initialize" feature /// pyo3::prepare_freethreaded_python(); /// /// Python::with_gil(|py| { /// pyo3_async_runtimes::async_std::run(py, async move { /// async_std::task::sleep(Duration::from_secs(1)).await; /// Ok(()) /// }) /// .map_err(|e| { /// e.print_and_set_sys_last_vars(py); /// }) /// .unwrap(); /// }) /// } /// ``` pub fn run(py: Python, fut: F) -> PyResult where F: Future> + Send + 'static, T: Send + Sync + 'static, { generic::run::(py, fut) } /// Convert a Rust Future into a Python awaitable /// /// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`, /// the Rust future will be cancelled as well (new behaviour in `v0.15`). /// /// Python `contextvars` are preserved when calling async Python functions within the Rust future /// via [`into_future`] (new behaviour in `v0.15`). /// /// > Although `contextvars` are preserved for async Python functions, synchronous functions will /// > unfortunately fail to resolve them when called within the Rust future. This is because the /// > function is being called from a Rust thread, not inside an actual Python coroutine context. /// > /// > As a workaround, you can get the `contextvars` from the current task locals using /// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your /// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the /// > synchronous function, and restore the previous context when it returns or raises an exception. /// /// # Arguments /// * `py` - PyO3 GIL guard /// * `locals` - The task locals for the given future /// * `fut` - The Rust future to be converted /// /// # Examples /// /// ``` /// use std::time::Duration; /// /// use pyo3::prelude::*; /// /// /// Awaitable sleep function /// #[pyfunction] /// fn sleep_for<'p>(py: Python<'p>, secs: Bound<'p, PyAny>) -> PyResult> { /// let secs = secs.extract()?; /// pyo3_async_runtimes::async_std::future_into_py_with_locals( /// py, /// pyo3_async_runtimes::async_std::get_current_locals(py)?, /// async move { /// async_std::task::sleep(Duration::from_secs(secs)).await; /// Python::with_gil(|py| Ok(py.None())) /// } /// ) /// } /// ``` pub fn future_into_py_with_locals( py: Python, locals: TaskLocals, fut: F, ) -> PyResult> where F: Future> + Send + 'static, T: IntoPy, { generic::future_into_py_with_locals::(py, locals, fut) } /// Convert a Rust Future into a Python awaitable /// /// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`, /// the Rust future will be cancelled as well (new behaviour in `v0.15`). /// /// Python `contextvars` are preserved when calling async Python functions within the Rust future /// via [`into_future`] (new behaviour in `v0.15`). /// /// > Although `contextvars` are preserved for async Python functions, synchronous functions will /// > unfortunately fail to resolve them when called within the Rust future. This is because the /// > function is being called from a Rust thread, not inside an actual Python coroutine context. /// > /// > As a workaround, you can get the `contextvars` from the current task locals using /// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your /// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the /// > synchronous function, and restore the previous context when it returns or raises an exception. /// /// # Arguments /// * `py` - The current PyO3 GIL guard /// * `fut` - The Rust future to be converted /// /// # Examples /// /// ``` /// use std::time::Duration; /// /// use pyo3::prelude::*; /// /// /// Awaitable sleep function /// #[pyfunction] /// fn sleep_for<'p>(py: Python<'p>, secs: Bound<'p, PyAny>) -> PyResult> { /// let secs = secs.extract()?; /// pyo3_async_runtimes::async_std::future_into_py(py, async move { /// async_std::task::sleep(Duration::from_secs(secs)).await; /// Ok(()) /// }) /// } /// ``` pub fn future_into_py(py: Python, fut: F) -> PyResult> where F: Future> + Send + 'static, T: IntoPy, { generic::future_into_py::(py, fut) } /// Convert a `!Send` Rust Future into a Python awaitable /// /// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`, /// the Rust future will be cancelled as well (new behaviour in `v0.15`). /// /// Python `contextvars` are preserved when calling async Python functions within the Rust future /// via [`into_future`] (new behaviour in `v0.15`). /// /// > Although `contextvars` are preserved for async Python functions, synchronous functions will /// > unfortunately fail to resolve them when called within the Rust future. This is because the /// > function is being called from a Rust thread, not inside an actual Python coroutine context. /// > /// > As a workaround, you can get the `contextvars` from the current task locals using /// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your /// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the /// > synchronous function, and restore the previous context when it returns or raises an exception. /// /// # Arguments /// * `py` - PyO3 GIL guard /// * `locals` - The task locals for the given future /// * `fut` - The Rust future to be converted /// /// # Examples /// /// ``` /// use std::{rc::Rc, time::Duration}; /// /// use pyo3::prelude::*; /// /// /// Awaitable non-send sleep function /// #[pyfunction] /// fn sleep_for(py: Python, secs: u64) -> PyResult> { /// // Rc is non-send so it cannot be passed into pyo3_async_runtimes::async_std::future_into_py /// let secs = Rc::new(secs); /// Ok(pyo3_async_runtimes::async_std::local_future_into_py_with_locals( /// py, /// pyo3_async_runtimes::async_std::get_current_locals(py)?, /// async move { /// async_std::task::sleep(Duration::from_secs(*secs)).await; /// Python::with_gil(|py| Ok(py.None())) /// } /// )?.into()) /// } /// /// # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))] /// #[pyo3_async_runtimes::async_std::main] /// async fn main() -> PyResult<()> { /// Python::with_gil(|py| { /// let py_future = sleep_for(py, 1)?; /// pyo3_async_runtimes::async_std::into_future(py_future) /// })? /// .await?; /// /// Ok(()) /// } /// # #[cfg(not(all(feature = "async-std-runtime", feature = "attributes")))] /// # fn main() {} /// ``` #[deprecated( since = "0.18.0", note = "Questionable whether these conversions have real-world utility (see https://github.com/awestlake87/pyo3-asyncio/issues/59#issuecomment-1008038497 and let me know if you disagree!)" )] #[allow(deprecated)] pub fn local_future_into_py_with_locals( py: Python, locals: TaskLocals, fut: F, ) -> PyResult> where F: Future> + 'static, T: IntoPy, { generic::local_future_into_py_with_locals::(py, locals, fut) } /// Convert a `!Send` Rust Future into a Python awaitable /// /// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`, /// the Rust future will be cancelled as well (new behaviour in `v0.15`). /// /// Python `contextvars` are preserved when calling async Python functions within the Rust future /// via [`into_future`] (new behaviour in `v0.15`). /// /// > Although `contextvars` are preserved for async Python functions, synchronous functions will /// > unfortunately fail to resolve them when called within the Rust future. This is because the /// > function is being called from a Rust thread, not inside an actual Python coroutine context. /// > /// > As a workaround, you can get the `contextvars` from the current task locals using /// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your /// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the /// > synchronous function, and restore the previous context when it returns or raises an exception. /// /// # Arguments /// * `py` - The current PyO3 GIL guard /// * `fut` - The Rust future to be converted /// /// # Examples /// /// ``` /// use std::{rc::Rc, time::Duration}; /// /// use pyo3::prelude::*; /// /// /// Awaitable non-send sleep function /// #[pyfunction] /// fn sleep_for(py: Python, secs: u64) -> PyResult> { /// // Rc is non-send so it cannot be passed into pyo3_async_runtimes::async_std::future_into_py /// let secs = Rc::new(secs); /// pyo3_async_runtimes::async_std::local_future_into_py(py, async move { /// async_std::task::sleep(Duration::from_secs(*secs)).await; /// Ok(()) /// }) /// } /// /// # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))] /// #[pyo3_async_runtimes::async_std::main] /// async fn main() -> PyResult<()> { /// Python::with_gil(|py| { /// let py_future = sleep_for(py, 1)?; /// pyo3_async_runtimes::async_std::into_future(py_future) /// })? /// .await?; /// /// Ok(()) /// } /// # #[cfg(not(all(feature = "async-std-runtime", feature = "attributes")))] /// # fn main() {} /// ``` #[deprecated( since = "0.18.0", note = "Questionable whether these conversions have real-world utility (see https://github.com/awestlake87/pyo3-asyncio/issues/59#issuecomment-1008038497 and let me know if you disagree!)" )] #[allow(deprecated)] pub fn local_future_into_py(py: Python, fut: F) -> PyResult> where F: Future> + 'static, T: IntoPy, { generic::local_future_into_py::(py, fut) } /// Convert a Python `awaitable` into a Rust Future /// /// This function converts the `awaitable` into a Python Task using `run_coroutine_threadsafe`. A /// completion handler sends the result of this Task through a /// `futures::channel::oneshot::Sender>` and the future returned by this function /// simply awaits the result through the `futures::channel::oneshot::Receiver>`. /// /// # Arguments /// * `awaitable` - The Python `awaitable` to be converted /// /// # Examples /// /// ``` /// use std::time::Duration; /// /// use pyo3::prelude::*; /// /// const PYTHON_CODE: &'static str = r#" /// import asyncio /// /// async def py_sleep(duration): /// await asyncio.sleep(duration) /// "#; /// /// async fn py_sleep(seconds: f32) -> PyResult<()> { /// let test_mod = Python::with_gil(|py| -> PyResult { /// Ok( /// PyModule::from_code_bound( /// py, /// PYTHON_CODE, /// "test_into_future/test_mod.py", /// "test_mod" /// )? /// .into() /// ) /// })?; /// /// Python::with_gil(|py| { /// pyo3_async_runtimes::async_std::into_future( /// test_mod /// .call_method1(py, "py_sleep", (seconds.into_py(py),))? /// .into_bound(py), /// ) /// })? /// .await?; /// Ok(()) /// } /// ``` pub fn into_future( awaitable: Bound, ) -> PyResult> + Send> { generic::into_future::(awaitable) } /// unstable-streams Convert an async generator into a stream /// /// **This API is marked as unstable** and is only available when the /// `unstable-streams` crate feature is enabled. This comes with no /// stability guarantees, and could be changed or removed at any time. /// /// # Arguments /// * `gen` - The Python async generator to be converted /// /// # Examples /// ``` /// use pyo3::prelude::*; /// use futures::{StreamExt, TryStreamExt}; /// /// const TEST_MOD: &str = r#" /// import asyncio /// /// async def gen(): /// for i in range(10): /// await asyncio.sleep(0.1) /// yield i /// "#; /// /// # #[cfg(all(feature = "unstable-streams", feature = "attributes"))] /// # #[pyo3_async_runtimes::async_std::main] /// # async fn main() -> PyResult<()> { /// let stream = Python::with_gil(|py| { /// let test_mod = PyModule::from_code_bound( /// py, /// TEST_MOD, /// "test_rust_coroutine/test_mod.py", /// "test_mod", /// )?; /// /// pyo3_async_runtimes::async_std::into_stream_v1(test_mod.call_method0("gen")?) /// })?; /// /// let vals = stream /// .map(|item| Python::with_gil(|py| -> PyResult { Ok(item?.bind(py).extract()?) })) /// .try_collect::>() /// .await?; /// /// assert_eq!((0..10).collect::>(), vals); /// /// Ok(()) /// # } /// # #[cfg(not(all(feature = "unstable-streams", feature = "attributes")))] /// # fn main() {} /// ``` #[cfg(feature = "unstable-streams")] pub fn into_stream_v1( gen: Bound<'_, PyAny>, ) -> PyResult> + 'static> { generic::into_stream_v1::(gen) } /// unstable-streams Convert an async generator into a stream /// /// **This API is marked as unstable** and is only available when the /// `unstable-streams` crate feature is enabled. This comes with no /// stability guarantees, and could be changed or removed at any time. /// /// # Arguments /// * `locals` - The current task locals /// * `gen` - The Python async generator to be converted /// /// # Examples /// ``` /// use pyo3::prelude::*; /// use futures::{StreamExt, TryStreamExt}; /// /// const TEST_MOD: &str = r#" /// import asyncio /// /// async def gen(): /// for i in range(10): /// await asyncio.sleep(0.1) /// yield i /// "#; /// /// # #[cfg(all(feature = "unstable-streams", feature = "attributes"))] /// # #[pyo3_async_runtimes::async_std::main] /// # async fn main() -> PyResult<()> { /// let stream = Python::with_gil(|py| { /// let test_mod = PyModule::from_code_bound( /// py, /// TEST_MOD, /// "test_rust_coroutine/test_mod.py", /// "test_mod", /// )?; /// /// pyo3_async_runtimes::async_std::into_stream_with_locals_v1( /// pyo3_async_runtimes::async_std::get_current_locals(py)?, /// test_mod.call_method0("gen")? /// ) /// })?; /// /// let vals = stream /// .map(|item| Python::with_gil(|py| -> PyResult { Ok(item?.bind(py).extract()?) })) /// .try_collect::>() /// .await?; /// /// assert_eq!((0..10).collect::>(), vals); /// /// Ok(()) /// # } /// # #[cfg(not(all(feature = "unstable-streams", feature = "attributes")))] /// # fn main() {} /// ``` #[cfg(feature = "unstable-streams")] pub fn into_stream_with_locals_v1( locals: TaskLocals, gen: Bound<'_, PyAny>, ) -> PyResult> + 'static> { generic::into_stream_with_locals_v1::(locals, gen) } /// unstable-streams Convert an async generator into a stream /// /// **This API is marked as unstable** and is only available when the /// `unstable-streams` crate feature is enabled. This comes with no /// stability guarantees, and could be changed or removed at any time. /// /// # Arguments /// * `locals` - The current task locals /// * `gen` - The Python async generator to be converted /// /// # Examples /// ``` /// use pyo3::prelude::*; /// use futures::{StreamExt, TryStreamExt}; /// /// const TEST_MOD: &str = r#" /// import asyncio /// /// async def gen(): /// for i in range(10): /// await asyncio.sleep(0.1) /// yield i /// "#; /// /// # #[cfg(all(feature = "unstable-streams", feature = "attributes"))] /// # #[pyo3_async_runtimes::async_std::main] /// # async fn main() -> PyResult<()> { /// let stream = Python::with_gil(|py| { /// let test_mod = PyModule::from_code_bound( /// py, /// TEST_MOD, /// "test_rust_coroutine/test_mod.py", /// "test_mod", /// )?; /// /// pyo3_async_runtimes::async_std::into_stream_with_locals_v2( /// pyo3_async_runtimes::async_std::get_current_locals(py)?, /// test_mod.call_method0("gen")? /// ) /// })?; /// /// let vals = stream /// .map(|item| Python::with_gil(|py| -> PyResult { Ok(item.bind(py).extract()?) })) /// .try_collect::>() /// .await?; /// /// assert_eq!((0..10).collect::>(), vals); /// /// Ok(()) /// # } /// # #[cfg(not(all(feature = "unstable-streams", feature = "attributes")))] /// # fn main() {} /// ``` #[cfg(feature = "unstable-streams")] pub fn into_stream_with_locals_v2( locals: TaskLocals, gen: Bound<'_, PyAny>, ) -> PyResult + 'static> { generic::into_stream_with_locals_v2::(locals, gen) } /// unstable-streams Convert an async generator into a stream /// /// **This API is marked as unstable** and is only available when the /// `unstable-streams` crate feature is enabled. This comes with no /// stability guarantees, and could be changed or removed at any time. /// /// # Arguments /// * `gen` - The Python async generator to be converted /// /// # Examples /// ``` /// use pyo3::prelude::*; /// use futures::{StreamExt, TryStreamExt}; /// /// const TEST_MOD: &str = r#" /// import asyncio /// /// async def gen(): /// for i in range(10): /// await asyncio.sleep(0.1) /// yield i /// "#; /// /// # #[cfg(all(feature = "unstable-streams", feature = "attributes"))] /// # #[pyo3_async_runtimes::async_std::main] /// # async fn main() -> PyResult<()> { /// let stream = Python::with_gil(|py| { /// let test_mod = PyModule::from_code_bound( /// py, /// TEST_MOD, /// "test_rust_coroutine/test_mod.py", /// "test_mod", /// )?; /// /// pyo3_async_runtimes::async_std::into_stream_v2(test_mod.call_method0("gen")?) /// })?; /// /// let vals = stream /// .map(|item| Python::with_gil(|py| -> PyResult { Ok(item.bind(py).extract()?) })) /// .try_collect::>() /// .await?; /// /// assert_eq!((0..10).collect::>(), vals); /// /// Ok(()) /// # } /// # #[cfg(not(all(feature = "unstable-streams", feature = "attributes")))] /// # fn main() {} /// ``` #[cfg(feature = "unstable-streams")] pub fn into_stream_v2( gen: Bound<'_, PyAny>, ) -> PyResult + 'static> { generic::into_stream_v2::(gen) } pyo3-async-runtimes-0.22.0/src/err.rs000064400000000000000000000004101046102023000154760ustar 00000000000000// FIXME - is there a way to document custom PyO3 exceptions? #[allow(missing_docs)] mod exceptions { use pyo3::{create_exception, exceptions::PyException}; create_exception!(pyo3_async_runtimes, RustPanic, PyException); } pub use exceptions::RustPanic; pyo3-async-runtimes-0.22.0/src/generic.rs000064400000000000000000001525711046102023000163420ustar 00000000000000//! Generic implementations of PyO3 Asyncio utilities that can be used for any Rust runtime //! //! Items marked with //! unstable-streams //! > are only available when the `unstable-streams` Cargo feature is enabled: //! //! ```toml //! [dependencies.pyo3-async-runtimes] //! version = "0.22" //! features = ["unstable-streams"] //! ``` use std::{ future::Future, pin::Pin, sync::{Arc, Mutex}, task::{Context, Poll}, }; use crate::{ asyncio, call_soon_threadsafe, close, create_future, dump_err, err::RustPanic, get_running_loop, into_future_with_locals, TaskLocals, }; use futures::channel::oneshot; #[cfg(feature = "unstable-streams")] use futures::{channel::mpsc, SinkExt}; #[cfg(feature = "unstable-streams")] use once_cell::sync::OnceCell; use pin_project_lite::pin_project; use pyo3::prelude::*; #[cfg(feature = "unstable-streams")] use std::marker::PhantomData; /// Generic utilities for a JoinError pub trait JoinError { /// Check if the spawned task exited because of a panic fn is_panic(&self) -> bool; /// Get the panic object associated with the error. Panics if `is_panic` is not true. fn into_panic(self) -> Box; } /// Generic Rust async/await runtime pub trait Runtime: Send + 'static { /// The error returned by a JoinHandle after being awaited type JoinError: JoinError + Send; /// A future that completes with the result of the spawned task type JoinHandle: Future> + Send; /// Spawn a future onto this runtime's event loop fn spawn(fut: F) -> Self::JoinHandle where F: Future + Send + 'static; } /// Extension trait for async/await runtimes that support spawning local tasks pub trait SpawnLocalExt: Runtime { /// Spawn a !Send future onto this runtime's event loop fn spawn_local(fut: F) -> Self::JoinHandle where F: Future + 'static; } /// Exposes the utilities necessary for using task-local data in the Runtime pub trait ContextExt: Runtime { /// Set the task locals for the given future fn scope(locals: TaskLocals, fut: F) -> Pin + Send>> where F: Future + Send + 'static; /// Get the task locals for the current task fn get_task_locals() -> Option; } /// Adds the ability to scope task-local data for !Send futures pub trait LocalContextExt: Runtime { /// Set the task locals for the given !Send future fn scope_local(locals: TaskLocals, fut: F) -> Pin>> where F: Future + 'static; } /// Get the current event loop from either Python or Rust async task local context /// /// This function first checks if the runtime has a task-local reference to the Python event loop. /// If not, it calls [`get_running_loop`](crate::get_running_loop`) to get the event loop associated /// with the current OS thread. pub fn get_current_loop(py: Python) -> PyResult> where R: ContextExt, { if let Some(locals) = R::get_task_locals() { Ok(locals.event_loop.into_bound(py)) } else { get_running_loop(py) } } /// Either copy the task locals from the current task OR get the current running loop and /// contextvars from Python. pub fn get_current_locals(py: Python) -> PyResult where R: ContextExt, { if let Some(locals) = R::get_task_locals() { Ok(locals) } else { Ok(TaskLocals::with_running_loop(py)?.copy_context(py)?) } } /// Run the event loop until the given Future completes /// /// After this function returns, the event loop can be resumed with [`run_until_complete`] /// /// # Arguments /// * `event_loop` - The Python event loop that should run the future /// * `fut` - The future to drive to completion /// /// # Examples /// /// ```no_run /// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_async_runtimes::{ /// # TaskLocals, /// # generic::{JoinError, SpawnLocalExt, ContextExt, LocalContextExt, Runtime} /// # }; /// # /// # struct MyCustomJoinError; /// # /// # impl JoinError for MyCustomJoinError { /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } /// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomJoinHandle; /// # /// # impl Future for MyCustomJoinHandle { /// # type Output = Result<(), MyCustomJoinError>; /// # /// # fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomRuntime; /// # /// # impl Runtime for MyCustomRuntime { /// # type JoinError = MyCustomJoinError; /// # type JoinHandle = MyCustomJoinHandle; /// # /// # fn spawn(fut: F) -> Self::JoinHandle /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # } /// # /// # impl ContextExt for MyCustomRuntime { /// # fn scope(locals: TaskLocals, fut: F) -> Pin + Send>> /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # fn get_task_locals() -> Option { /// # unreachable!() /// # } /// # } /// # /// # use std::time::Duration; /// # /// # use pyo3::prelude::*; /// # /// # Python::with_gil(|py| -> PyResult<()> { /// # let event_loop = py.import_bound("asyncio")?.call_method0("new_event_loop")?; /// # #[cfg(feature = "tokio-runtime")] /// pyo3_async_runtimes::generic::run_until_complete::(&event_loop, async move { /// tokio::time::sleep(Duration::from_secs(1)).await; /// Ok(()) /// })?; /// # Ok(()) /// # }).unwrap(); /// ``` pub fn run_until_complete(event_loop: &Bound, fut: F) -> PyResult where R: Runtime + ContextExt, F: Future> + Send + 'static, T: Send + Sync + 'static, { let py = event_loop.py(); let result_tx = Arc::new(Mutex::new(None)); let result_rx = Arc::clone(&result_tx); let coro = future_into_py_with_locals::( py, TaskLocals::new(event_loop.clone()).copy_context(py)?, async move { let val = fut.await?; if let Ok(mut result) = result_tx.lock() { *result = Some(val); } Ok(()) }, )?; event_loop.call_method1("run_until_complete", (coro,))?; let result = result_rx.lock().unwrap().take().unwrap(); Ok(result) } /// Run the event loop until the given Future completes /// /// # Arguments /// * `py` - The current PyO3 GIL guard /// * `fut` - The future to drive to completion /// /// # Examples /// /// ```no_run /// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_async_runtimes::{ /// # TaskLocals, /// # generic::{JoinError, SpawnLocalExt, ContextExt, LocalContextExt, Runtime} /// # }; /// # /// # struct MyCustomJoinError; /// # /// # impl JoinError for MyCustomJoinError { /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } /// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomJoinHandle; /// # /// # impl Future for MyCustomJoinHandle { /// # type Output = Result<(), MyCustomJoinError>; /// # /// # fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomRuntime; /// # /// # impl Runtime for MyCustomRuntime { /// # type JoinError = MyCustomJoinError; /// # type JoinHandle = MyCustomJoinHandle; /// # /// # fn spawn(fut: F) -> Self::JoinHandle /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # } /// # /// # impl ContextExt for MyCustomRuntime { /// # fn scope(locals: TaskLocals, fut: F) -> Pin + Send>> /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # fn get_task_locals() -> Option { /// # unreachable!() /// # } /// # } /// # /// # use std::time::Duration; /// # async fn custom_sleep(_duration: Duration) { } /// # /// # use pyo3::prelude::*; /// # /// fn main() { /// Python::with_gil(|py| { /// pyo3_async_runtimes::generic::run::(py, async move { /// custom_sleep(Duration::from_secs(1)).await; /// Ok(()) /// }) /// .map_err(|e| { /// e.print_and_set_sys_last_vars(py); /// }) /// .unwrap(); /// }) /// } /// ``` pub fn run(py: Python, fut: F) -> PyResult where R: Runtime + ContextExt, F: Future> + Send + 'static, T: Send + Sync + 'static, { let event_loop = asyncio(py)?.call_method0("new_event_loop")?; let result = run_until_complete::(&event_loop, fut); close(event_loop)?; result } fn cancelled(future: &Bound) -> PyResult { future.getattr("cancelled")?.call0()?.is_truthy() } #[pyclass] struct CheckedCompletor; #[pymethods] impl CheckedCompletor { fn __call__( &self, future: &Bound, complete: &Bound, value: &Bound, ) -> PyResult<()> { if cancelled(future)? { return Ok(()); } complete.call1((value,))?; Ok(()) } } fn set_result( event_loop: &Bound, future: &Bound, result: PyResult, ) -> PyResult<()> { let py = event_loop.py(); let none = py.None().into_bound(py); let (complete, val) = match result { Ok(val) => (future.getattr("set_result")?, val.into_py(py)), Err(err) => (future.getattr("set_exception")?, err.into_py(py)), }; call_soon_threadsafe(event_loop, &none, (CheckedCompletor, future, complete, val))?; Ok(()) } /// Convert a Python `awaitable` into a Rust Future /// /// This function simply forwards the future and the task locals returned by [`get_current_locals`] /// to [`into_future_with_locals`](`crate::into_future_with_locals`). See /// [`into_future_with_locals`](`crate::into_future_with_locals`) for more details. /// /// # Arguments /// * `awaitable` - The Python `awaitable` to be converted /// /// # Examples /// /// ```no_run /// # use std::{any::Any, pin::Pin, future::Future, task::{Context, Poll}, time::Duration}; /// # /// # use pyo3::prelude::*; /// # /// # use pyo3_async_runtimes::{ /// # TaskLocals, /// # generic::{JoinError, SpawnLocalExt, ContextExt, LocalContextExt, Runtime} /// # }; /// # /// # struct MyCustomJoinError; /// # /// # impl JoinError for MyCustomJoinError { /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } /// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomJoinHandle; /// # /// # impl Future for MyCustomJoinHandle { /// # type Output = Result<(), MyCustomJoinError>; /// # /// # fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomRuntime; /// # /// # impl MyCustomRuntime { /// # async fn sleep(_: Duration) { /// # unreachable!() /// # } /// # } /// # /// # impl Runtime for MyCustomRuntime { /// # type JoinError = MyCustomJoinError; /// # type JoinHandle = MyCustomJoinHandle; /// # /// # fn spawn(fut: F) -> Self::JoinHandle /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # } /// # /// # impl ContextExt for MyCustomRuntime { /// # fn scope(locals: TaskLocals, fut: F) -> Pin + Send>> /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # fn get_task_locals() -> Option { /// # unreachable!() /// # } /// # } /// # /// const PYTHON_CODE: &'static str = r#" /// import asyncio /// /// async def py_sleep(duration): /// await asyncio.sleep(duration) /// "#; /// /// async fn py_sleep(seconds: f32) -> PyResult<()> { /// let test_mod = Python::with_gil(|py| -> PyResult { /// Ok( /// PyModule::from_code_bound( /// py, /// PYTHON_CODE, /// "test_into_future/test_mod.py", /// "test_mod" /// )? /// .into() /// ) /// })?; /// /// Python::with_gil(|py| { /// pyo3_async_runtimes::generic::into_future::( /// test_mod /// .call_method1(py, "py_sleep", (seconds.into_py(py),))? /// .into_bound(py), /// ) /// })? /// .await?; /// Ok(()) /// } /// ``` pub fn into_future( awaitable: Bound, ) -> PyResult> + Send> where R: Runtime + ContextExt, { into_future_with_locals(&get_current_locals::(awaitable.py())?, awaitable) } /// Convert a Rust Future into a Python awaitable with a generic runtime /// /// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`, /// the Rust future will be cancelled as well (new behaviour in `v0.15`). /// /// Python `contextvars` are preserved when calling async Python functions within the Rust future /// via [`into_future`] (new behaviour in `v0.15`). /// /// > Although `contextvars` are preserved for async Python functions, synchronous functions will /// > unfortunately fail to resolve them when called within the Rust future. This is because the /// > function is being called from a Rust thread, not inside an actual Python coroutine context. /// > /// > As a workaround, you can get the `contextvars` from the current task locals using /// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your /// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the /// > synchronous function, and restore the previous context when it returns or raises an exception. /// /// # Arguments /// * `py` - PyO3 GIL guard /// * `locals` - The task-local data for Python /// * `fut` - The Rust future to be converted /// /// # Examples /// /// ```no_run /// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_async_runtimes::{ /// # TaskLocals, /// # generic::{JoinError, SpawnLocalExt, ContextExt, LocalContextExt, Runtime} /// # }; /// # /// # struct MyCustomJoinError; /// # /// # impl JoinError for MyCustomJoinError { /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } /// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomJoinHandle; /// # /// # impl Future for MyCustomJoinHandle { /// # type Output = Result<(), MyCustomJoinError>; /// # /// # fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomRuntime; /// # /// # impl MyCustomRuntime { /// # async fn sleep(_: Duration) { /// # unreachable!() /// # } /// # } /// # /// # impl Runtime for MyCustomRuntime { /// # type JoinError = MyCustomJoinError; /// # type JoinHandle = MyCustomJoinHandle; /// # /// # fn spawn(fut: F) -> Self::JoinHandle /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # } /// # /// # impl ContextExt for MyCustomRuntime { /// # fn scope(locals: TaskLocals, fut: F) -> Pin + Send>> /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # fn get_task_locals() -> Option { /// # unreachable!() /// # } /// # } /// # /// use std::time::Duration; /// /// use pyo3::prelude::*; /// /// /// Awaitable sleep function /// #[pyfunction] /// fn sleep_for<'p>(py: Python<'p>, secs: Bound<'p, PyAny>) -> PyResult> { /// let secs = secs.extract()?; /// pyo3_async_runtimes::generic::future_into_py_with_locals::( /// py, /// pyo3_async_runtimes::generic::get_current_locals::(py)?, /// async move { /// MyCustomRuntime::sleep(Duration::from_secs(secs)).await; /// Ok(()) /// } /// ) /// } /// ``` #[allow(unused_must_use)] pub fn future_into_py_with_locals( py: Python, locals: TaskLocals, fut: F, ) -> PyResult> where R: Runtime + ContextExt, F: Future> + Send + 'static, T: IntoPy, { let (cancel_tx, cancel_rx) = oneshot::channel(); let py_fut = create_future(locals.event_loop.bind(py).clone())?; py_fut.call_method1( "add_done_callback", (PyDoneCallback { cancel_tx: Some(cancel_tx), },), )?; let future_tx1 = PyObject::from(py_fut.clone()); let future_tx2 = future_tx1.clone_ref(py); R::spawn(async move { let locals2 = Python::with_gil(|py| locals.clone_ref(py)); if let Err(e) = R::spawn(async move { let result = R::scope( Python::with_gil(|py| locals2.clone_ref(py)), Cancellable::new_with_cancel_rx(fut, cancel_rx), ) .await; Python::with_gil(move |py| { if cancelled(future_tx1.bind(py)) .map_err(dump_err(py)) .unwrap_or(false) { return; } let _ = set_result( &locals2.event_loop(py), future_tx1.bind(py), result.map(|val| val.into_py(py)), ) .map_err(dump_err(py)); }); }) .await { if e.is_panic() { Python::with_gil(move |py| { if cancelled(future_tx2.bind(py)) .map_err(dump_err(py)) .unwrap_or(false) { return; } let panic_message = format!( "rust future panicked: {}", get_panic_message(&e.into_panic()) ); let _ = set_result( locals.event_loop.bind(py), future_tx2.bind(py), Err(RustPanic::new_err(panic_message)), ) .map_err(dump_err(py)); }); } } }); Ok(py_fut) } fn get_panic_message(any: &dyn std::any::Any) -> &str { if let Some(str_slice) = any.downcast_ref::<&str>() { str_slice } else if let Some(string) = any.downcast_ref::() { string.as_str() } else { "unknown error" } } pin_project! { /// Future returned by [`timeout`](timeout) and [`timeout_at`](timeout_at). #[must_use = "futures do nothing unless you `.await` or poll them"] #[derive(Debug)] struct Cancellable { #[pin] future: T, #[pin] cancel_rx: oneshot::Receiver<()>, poll_cancel_rx: bool } } impl Cancellable { fn new_with_cancel_rx(future: T, cancel_rx: oneshot::Receiver<()>) -> Self { Self { future, cancel_rx, poll_cancel_rx: true, } } } impl Future for Cancellable where F: Future>, T: IntoPy, { type Output = F::Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); // First, try polling the future if let Poll::Ready(v) = this.future.poll(cx) { return Poll::Ready(v); } // Now check for cancellation if *this.poll_cancel_rx { match this.cancel_rx.poll(cx) { Poll::Ready(Ok(())) => { *this.poll_cancel_rx = false; // The python future has already been cancelled, so this return value will never // be used. Poll::Ready(Err(pyo3::exceptions::PyBaseException::new_err( "unreachable", ))) } Poll::Ready(Err(_)) => { *this.poll_cancel_rx = false; Poll::Pending } Poll::Pending => Poll::Pending, } } else { Poll::Pending } } } #[pyclass] struct PyDoneCallback { cancel_tx: Option>, } #[pymethods] impl PyDoneCallback { pub fn __call__(&mut self, fut: &Bound) -> PyResult<()> { let py = fut.py(); if cancelled(fut).map_err(dump_err(py)).unwrap_or(false) { let _ = self.cancel_tx.take().unwrap().send(()); } Ok(()) } } /// Convert a Rust Future into a Python awaitable with a generic runtime /// /// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`, /// the Rust future will be cancelled as well (new behaviour in `v0.15`). /// /// Python `contextvars` are preserved when calling async Python functions within the Rust future /// via [`into_future`] (new behaviour in `v0.15`). /// /// > Although `contextvars` are preserved for async Python functions, synchronous functions will /// > unfortunately fail to resolve them when called within the Rust future. This is because the /// > function is being called from a Rust thread, not inside an actual Python coroutine context. /// > /// > As a workaround, you can get the `contextvars` from the current task locals using /// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your /// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the /// > synchronous function, and restore the previous context when it returns or raises an exception. /// /// # Arguments /// * `py` - The current PyO3 GIL guard /// * `fut` - The Rust future to be converted /// /// # Examples /// /// ```no_run /// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_async_runtimes::{ /// # TaskLocals, /// # generic::{JoinError, SpawnLocalExt, ContextExt, LocalContextExt, Runtime} /// # }; /// # /// # struct MyCustomJoinError; /// # /// # impl JoinError for MyCustomJoinError { /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } /// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomJoinHandle; /// # /// # impl Future for MyCustomJoinHandle { /// # type Output = Result<(), MyCustomJoinError>; /// # /// # fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomRuntime; /// # /// # impl MyCustomRuntime { /// # async fn sleep(_: Duration) { /// # unreachable!() /// # } /// # } /// # /// # impl Runtime for MyCustomRuntime { /// # type JoinError = MyCustomJoinError; /// # type JoinHandle = MyCustomJoinHandle; /// # /// # fn spawn(fut: F) -> Self::JoinHandle /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # } /// # /// # impl ContextExt for MyCustomRuntime { /// # fn scope(locals: TaskLocals, fut: F) -> Pin + Send>> /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # fn get_task_locals() -> Option { /// # unreachable!() /// # } /// # } /// # /// use std::time::Duration; /// /// use pyo3::prelude::*; /// /// /// Awaitable sleep function /// #[pyfunction] /// fn sleep_for<'p>(py: Python<'p>, secs: Bound<'p, PyAny>) -> PyResult> { /// let secs = secs.extract()?; /// pyo3_async_runtimes::generic::future_into_py::(py, async move { /// MyCustomRuntime::sleep(Duration::from_secs(secs)).await; /// Ok(()) /// }) /// } /// ``` pub fn future_into_py(py: Python, fut: F) -> PyResult> where R: Runtime + ContextExt, F: Future> + Send + 'static, T: IntoPy, { future_into_py_with_locals::(py, get_current_locals::(py)?, fut) } /// Convert a `!Send` Rust Future into a Python awaitable with a generic runtime and manual /// specification of task locals. /// /// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`, /// the Rust future will be cancelled as well (new behaviour in `v0.15`). /// /// Python `contextvars` are preserved when calling async Python functions within the Rust future /// via [`into_future`] (new behaviour in `v0.15`). /// /// > Although `contextvars` are preserved for async Python functions, synchronous functions will /// > unfortunately fail to resolve them when called within the Rust future. This is because the /// > function is being called from a Rust thread, not inside an actual Python coroutine context. /// > /// > As a workaround, you can get the `contextvars` from the current task locals using /// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your /// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the /// > synchronous function, and restore the previous context when it returns or raises an exception. /// /// # Arguments /// * `py` - PyO3 GIL guard /// * `locals` - The task locals for the future /// * `fut` - The Rust future to be converted /// /// # Examples /// /// ```no_run /// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_async_runtimes::{ /// # TaskLocals, /// # generic::{JoinError, SpawnLocalExt, ContextExt, LocalContextExt, Runtime} /// # }; /// # /// # struct MyCustomJoinError; /// # /// # impl JoinError for MyCustomJoinError { /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } /// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomJoinHandle; /// # /// # impl Future for MyCustomJoinHandle { /// # type Output = Result<(), MyCustomJoinError>; /// # /// # fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomRuntime; /// # /// # impl MyCustomRuntime { /// # async fn sleep(_: Duration) { /// # unreachable!() /// # } /// # } /// # /// # impl Runtime for MyCustomRuntime { /// # type JoinError = MyCustomJoinError; /// # type JoinHandle = MyCustomJoinHandle; /// # /// # fn spawn(fut: F) -> Self::JoinHandle /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # } /// # /// # impl ContextExt for MyCustomRuntime { /// # fn scope(locals: TaskLocals, fut: F) -> Pin + Send>> /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # fn get_task_locals() -> Option { /// # unreachable!() /// # } /// # } /// # /// # impl SpawnLocalExt for MyCustomRuntime { /// # fn spawn_local(fut: F) -> Self::JoinHandle /// # where /// # F: Future + 'static /// # { /// # unreachable!() /// # } /// # } /// # /// # impl LocalContextExt for MyCustomRuntime { /// # fn scope_local(locals: TaskLocals, fut: F) -> Pin>> /// # where /// # F: Future + 'static /// # { /// # unreachable!() /// # } /// # } /// # /// use std::{rc::Rc, time::Duration}; /// /// use pyo3::prelude::*; /// /// /// Awaitable sleep function /// #[pyfunction] /// fn sleep_for(py: Python, secs: u64) -> PyResult> { /// // Rc is !Send so it cannot be passed into pyo3_async_runtimes::generic::future_into_py /// let secs = Rc::new(secs); /// /// pyo3_async_runtimes::generic::local_future_into_py_with_locals::( /// py, /// pyo3_async_runtimes::generic::get_current_locals::(py)?, /// async move { /// MyCustomRuntime::sleep(Duration::from_secs(*secs)).await; /// Ok(()) /// } /// ) /// } /// ``` #[deprecated( since = "0.18.0", note = "Questionable whether these conversions have real-world utility (see https://github.com/awestlake87/pyo3-asyncio/issues/59#issuecomment-1008038497 and let me know if you disagree!)" )] #[allow(unused_must_use)] pub fn local_future_into_py_with_locals( py: Python, locals: TaskLocals, fut: F, ) -> PyResult> where R: Runtime + SpawnLocalExt + LocalContextExt, F: Future> + 'static, T: IntoPy, { let (cancel_tx, cancel_rx) = oneshot::channel(); let py_fut = create_future(locals.event_loop.clone_ref(py).into_bound(py))?; py_fut.call_method1( "add_done_callback", (PyDoneCallback { cancel_tx: Some(cancel_tx), },), )?; let future_tx1 = PyObject::from(py_fut.clone()); let future_tx2 = future_tx1.clone_ref(py); R::spawn_local(async move { let locals2 = Python::with_gil(|py| locals.clone_ref(py)); if let Err(e) = R::spawn_local(async move { let result = R::scope_local( Python::with_gil(|py| locals2.clone_ref(py)), Cancellable::new_with_cancel_rx(fut, cancel_rx), ) .await; Python::with_gil(move |py| { if cancelled(future_tx1.bind(py)) .map_err(dump_err(py)) .unwrap_or(false) { return; } let _ = set_result( locals2.event_loop.bind(py), future_tx1.bind(py), result.map(|val| val.into_py(py)), ) .map_err(dump_err(py)); }); }) .await { if e.is_panic() { Python::with_gil(move |py| { if cancelled(future_tx2.bind(py)) .map_err(dump_err(py)) .unwrap_or(false) { return; } let panic_message = format!( "rust future panicked: {}", get_panic_message(&e.into_panic()) ); let _ = set_result( locals.event_loop.bind(py), future_tx2.bind(py), Err(RustPanic::new_err(panic_message)), ) .map_err(dump_err(py)); }); } } }); Ok(py_fut) } /// Convert a `!Send` Rust Future into a Python awaitable with a generic runtime /// /// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`, /// the Rust future will be cancelled as well (new behaviour in `v0.15`). /// /// Python `contextvars` are preserved when calling async Python functions within the Rust future /// via [`into_future`] (new behaviour in `v0.15`). /// /// > Although `contextvars` are preserved for async Python functions, synchronous functions will /// > unfortunately fail to resolve them when called within the Rust future. This is because the /// > function is being called from a Rust thread, not inside an actual Python coroutine context. /// > /// > As a workaround, you can get the `contextvars` from the current task locals using /// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your /// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the /// > synchronous function, and restore the previous context when it returns or raises an exception. /// /// # Arguments /// * `py` - The current PyO3 GIL guard /// * `fut` - The Rust future to be converted /// /// # Examples /// /// ```no_run /// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_async_runtimes::{ /// # TaskLocals, /// # generic::{JoinError, SpawnLocalExt, ContextExt, LocalContextExt, Runtime} /// # }; /// # /// # struct MyCustomJoinError; /// # /// # impl JoinError for MyCustomJoinError { /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } /// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomJoinHandle; /// # /// # impl Future for MyCustomJoinHandle { /// # type Output = Result<(), MyCustomJoinError>; /// # /// # fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomRuntime; /// # /// # impl MyCustomRuntime { /// # async fn sleep(_: Duration) { /// # unreachable!() /// # } /// # } /// # /// # impl Runtime for MyCustomRuntime { /// # type JoinError = MyCustomJoinError; /// # type JoinHandle = MyCustomJoinHandle; /// # /// # fn spawn(fut: F) -> Self::JoinHandle /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # } /// # /// # impl ContextExt for MyCustomRuntime { /// # fn scope(locals: TaskLocals, fut: F) -> Pin + Send>> /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # fn get_task_locals() -> Option { /// # unreachable!() /// # } /// # } /// # /// # impl SpawnLocalExt for MyCustomRuntime { /// # fn spawn_local(fut: F) -> Self::JoinHandle /// # where /// # F: Future + 'static /// # { /// # unreachable!() /// # } /// # } /// # /// # impl LocalContextExt for MyCustomRuntime { /// # fn scope_local(locals: TaskLocals, fut: F) -> Pin>> /// # where /// # F: Future + 'static /// # { /// # unreachable!() /// # } /// # } /// # /// use std::{rc::Rc, time::Duration}; /// /// use pyo3::prelude::*; /// /// /// Awaitable sleep function /// #[pyfunction] /// fn sleep_for(py: Python, secs: u64) -> PyResult> { /// // Rc is !Send so it cannot be passed into pyo3_async_runtimes::generic::future_into_py /// let secs = Rc::new(secs); /// /// pyo3_async_runtimes::generic::local_future_into_py::(py, async move { /// MyCustomRuntime::sleep(Duration::from_secs(*secs)).await; /// Ok(()) /// }) /// } /// ``` #[deprecated( since = "0.18.0", note = "Questionable whether these conversions have real-world utility (see https://github.com/awestlake87/pyo3-asyncio/issues/59#issuecomment-1008038497 and let me know if you disagree!)" )] #[allow(deprecated)] pub fn local_future_into_py(py: Python, fut: F) -> PyResult> where R: Runtime + ContextExt + SpawnLocalExt + LocalContextExt, F: Future> + 'static, T: IntoPy, { local_future_into_py_with_locals::(py, get_current_locals::(py)?, fut) } /// unstable-streams Convert an async generator into a stream /// /// **This API is marked as unstable** and is only available when the /// `unstable-streams` crate feature is enabled. This comes with no /// stability guarantees, and could be changed or removed at any time. /// /// # Arguments /// * `locals` - The current task locals /// * `gen` - The Python async generator to be converted /// /// # Examples /// ```no_run /// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_async_runtimes::{ /// # TaskLocals, /// # generic::{JoinError, ContextExt, Runtime} /// # }; /// # /// # struct MyCustomJoinError; /// # /// # impl JoinError for MyCustomJoinError { /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } /// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomJoinHandle; /// # /// # impl Future for MyCustomJoinHandle { /// # type Output = Result<(), MyCustomJoinError>; /// # /// # fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomRuntime; /// # /// # impl Runtime for MyCustomRuntime { /// # type JoinError = MyCustomJoinError; /// # type JoinHandle = MyCustomJoinHandle; /// # /// # fn spawn(fut: F) -> Self::JoinHandle /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # } /// # /// # impl ContextExt for MyCustomRuntime { /// # fn scope(locals: TaskLocals, fut: F) -> Pin + Send>> /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # fn get_task_locals() -> Option { /// # unreachable!() /// # } /// # } /// /// use pyo3::prelude::*; /// use futures::{StreamExt, TryStreamExt}; /// /// const TEST_MOD: &str = r#" /// import asyncio /// /// async def gen(): /// for i in range(10): /// await asyncio.sleep(0.1) /// yield i /// "#; /// /// # async fn test_async_gen() -> PyResult<()> { /// let stream = Python::with_gil(|py| { /// let test_mod = PyModule::from_code_bound( /// py, /// TEST_MOD, /// "test_rust_coroutine/test_mod.py", /// "test_mod", /// )?; /// /// pyo3_async_runtimes::generic::into_stream_with_locals_v1::( /// pyo3_async_runtimes::generic::get_current_locals::(py)?, /// test_mod.call_method0("gen")? /// ) /// })?; /// /// let vals = stream /// .map(|item| Python::with_gil(|py| -> PyResult { Ok(item?.bind(py).extract()?) })) /// .try_collect::>() /// .await?; /// /// assert_eq!((0..10).collect::>(), vals); /// /// Ok(()) /// # } /// ``` #[cfg(feature = "unstable-streams")] #[allow(unused_must_use)] // False positive unused lint on `R::spawn` pub fn into_stream_with_locals_v1( locals: TaskLocals, gen: Bound<'_, PyAny>, ) -> PyResult> + 'static> where R: Runtime, { let (tx, rx) = async_channel::bounded(1); let anext = PyObject::from(gen.getattr("__anext__")?); R::spawn(async move { loop { let fut = Python::with_gil(|py| -> PyResult<_> { into_future_with_locals(&locals, anext.bind(py).call0()?) }); let item = match fut { Ok(fut) => match fut.await { Ok(item) => Ok(item), Err(e) => { let stop_iter = Python::with_gil(|py| { e.is_instance_of::(py) }); if stop_iter { // end the iteration break; } else { Err(e) } } }, Err(e) => Err(e), }; if tx.send(item).await.is_err() { // receiving side was dropped break; } } }); Ok(rx) } /// unstable-streams Convert an async generator into a stream /// /// **This API is marked as unstable** and is only available when the /// `unstable-streams` crate feature is enabled. This comes with no /// stability guarantees, and could be changed or removed at any time. /// /// # Arguments /// * `gen` - The Python async generator to be converted /// /// # Examples /// ```no_run /// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_async_runtimes::{ /// # TaskLocals, /// # generic::{JoinError, ContextExt, Runtime} /// # }; /// # /// # struct MyCustomJoinError; /// # /// # impl JoinError for MyCustomJoinError { /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } /// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomJoinHandle; /// # /// # impl Future for MyCustomJoinHandle { /// # type Output = Result<(), MyCustomJoinError>; /// # /// # fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomRuntime; /// # /// # impl Runtime for MyCustomRuntime { /// # type JoinError = MyCustomJoinError; /// # type JoinHandle = MyCustomJoinHandle; /// # /// # fn spawn(fut: F) -> Self::JoinHandle /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # } /// # /// # impl ContextExt for MyCustomRuntime { /// # fn scope(locals: TaskLocals, fut: F) -> Pin + Send>> /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # fn get_task_locals() -> Option { /// # unreachable!() /// # } /// # } /// /// use pyo3::prelude::*; /// use futures::{StreamExt, TryStreamExt}; /// /// const TEST_MOD: &str = r#" /// import asyncio /// /// async def gen(): /// for i in range(10): /// await asyncio.sleep(0.1) /// yield i /// "#; /// /// # async fn test_async_gen() -> PyResult<()> { /// let stream = Python::with_gil(|py| { /// let test_mod = PyModule::from_code_bound( /// py, /// TEST_MOD, /// "test_rust_coroutine/test_mod.py", /// "test_mod", /// )?; /// /// pyo3_async_runtimes::generic::into_stream_v1::(test_mod.call_method0("gen")?) /// })?; /// /// let vals = stream /// .map(|item| Python::with_gil(|py| -> PyResult { Ok(item?.bind(py).extract()?) })) /// .try_collect::>() /// .await?; /// /// assert_eq!((0..10).collect::>(), vals); /// /// Ok(()) /// # } /// ``` #[cfg(feature = "unstable-streams")] pub fn into_stream_v1( gen: Bound<'_, PyAny>, ) -> PyResult> + 'static> where R: Runtime + ContextExt, { into_stream_with_locals_v1::(get_current_locals::(gen.py())?, gen) } trait Sender: Send + 'static { fn send(&mut self, py: Python, locals: TaskLocals, item: PyObject) -> PyResult; fn close(&mut self) -> PyResult<()>; } #[cfg(feature = "unstable-streams")] struct GenericSender where R: Runtime, { runtime: PhantomData, tx: mpsc::Sender, } #[cfg(feature = "unstable-streams")] impl Sender for GenericSender where R: Runtime + ContextExt, { fn send(&mut self, py: Python, locals: TaskLocals, item: PyObject) -> PyResult { match self.tx.try_send(item.clone_ref(py)) { Ok(_) => Ok(true.into_py(py)), Err(e) => { if e.is_full() { let mut tx = self.tx.clone(); Python::with_gil(move |py| { Ok( future_into_py_with_locals::(py, locals, async move { if tx.flush().await.is_err() { // receiving side disconnected return Python::with_gil(|py| Ok(false.into_py(py))); } if tx.send(item).await.is_err() { // receiving side disconnected return Python::with_gil(|py| Ok(false.into_py(py))); } Python::with_gil(|py| Ok(true.into_py(py))) })? .into(), ) }) } else { Ok(false.into_py(py)) } } } } fn close(&mut self) -> PyResult<()> { self.tx.close_channel(); Ok(()) } } #[pyclass] struct SenderGlue { locals: TaskLocals, tx: Box, } #[pymethods] impl SenderGlue { pub fn send(&mut self, item: PyObject) -> PyResult { Python::with_gil(|py| self.tx.send(py, self.locals.clone_ref(py), item)) } pub fn close(&mut self) -> PyResult<()> { self.tx.close() } } #[cfg(feature = "unstable-streams")] const STREAM_GLUE: &str = r#" import asyncio async def forward(gen, sender): async for item in gen: should_continue = sender.send(item) if asyncio.iscoroutine(should_continue): should_continue = await should_continue if should_continue: continue else: break sender.close() "#; /// unstable-streams Convert an async generator into a stream /// /// **This API is marked as unstable** and is only available when the /// `unstable-streams` crate feature is enabled. This comes with no /// stability guarantees, and could be changed or removed at any time. /// /// # Arguments /// * `locals` - The current task locals /// * `gen` - The Python async generator to be converted /// /// # Examples /// ```no_run /// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_async_runtimes::{ /// # TaskLocals, /// # generic::{JoinError, ContextExt, Runtime} /// # }; /// # /// # struct MyCustomJoinError; /// # /// # impl JoinError for MyCustomJoinError { /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } /// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomJoinHandle; /// # /// # impl Future for MyCustomJoinHandle { /// # type Output = Result<(), MyCustomJoinError>; /// # /// # fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomRuntime; /// # /// # impl Runtime for MyCustomRuntime { /// # type JoinError = MyCustomJoinError; /// # type JoinHandle = MyCustomJoinHandle; /// # /// # fn spawn(fut: F) -> Self::JoinHandle /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # } /// # /// # impl ContextExt for MyCustomRuntime { /// # fn scope(locals: TaskLocals, fut: F) -> Pin + Send>> /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # fn get_task_locals() -> Option { /// # unreachable!() /// # } /// # } /// /// use pyo3::prelude::*; /// use futures::{StreamExt, TryStreamExt}; /// /// const TEST_MOD: &str = r#" /// import asyncio /// /// async def gen(): /// for i in range(10): /// await asyncio.sleep(0.1) /// yield i /// "#; /// /// # async fn test_async_gen() -> PyResult<()> { /// let stream = Python::with_gil(|py| { /// let test_mod = PyModule::from_code_bound( /// py, /// TEST_MOD, /// "test_rust_coroutine/test_mod.py", /// "test_mod", /// )?; /// /// pyo3_async_runtimes::generic::into_stream_with_locals_v2::( /// pyo3_async_runtimes::generic::get_current_locals::(py)?, /// test_mod.call_method0("gen")? /// ) /// })?; /// /// let vals = stream /// .map(|item| Python::with_gil(|py| -> PyResult { Ok(item.bind(py).extract()?) })) /// .try_collect::>() /// .await?; /// /// assert_eq!((0..10).collect::>(), vals); /// /// Ok(()) /// # } /// ``` #[cfg(feature = "unstable-streams")] pub fn into_stream_with_locals_v2( locals: TaskLocals, gen: Bound<'_, PyAny>, ) -> PyResult + 'static> where R: Runtime + ContextExt, { static GLUE_MOD: OnceCell = OnceCell::new(); let py = gen.py(); let glue = GLUE_MOD .get_or_try_init(|| -> PyResult { Ok(PyModule::from_code_bound( py, STREAM_GLUE, "pyo3_async_runtimes/pyo3_async_runtimes_glue.py", "pyo3_async_runtimes_glue", )? .into()) })? .bind(py); let (tx, rx) = mpsc::channel(10); locals.event_loop(py).call_method1( "call_soon_threadsafe", ( locals.event_loop(py).getattr("create_task")?, glue.call_method1( "forward", ( gen, SenderGlue { locals, tx: Box::new(GenericSender { runtime: PhantomData::, tx, }), }, ), )?, ), )?; Ok(rx) } /// unstable-streams Convert an async generator into a stream /// /// **This API is marked as unstable** and is only available when the /// `unstable-streams` crate feature is enabled. This comes with no /// stability guarantees, and could be changed or removed at any time. /// /// # Arguments /// * `gen` - The Python async generator to be converted /// /// # Examples /// ```no_run /// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_async_runtimes::{ /// # TaskLocals, /// # generic::{JoinError, ContextExt, Runtime} /// # }; /// # /// # struct MyCustomJoinError; /// # /// # impl JoinError for MyCustomJoinError { /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } /// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomJoinHandle; /// # /// # impl Future for MyCustomJoinHandle { /// # type Output = Result<(), MyCustomJoinError>; /// # /// # fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { /// # unreachable!() /// # } /// # } /// # /// # struct MyCustomRuntime; /// # /// # impl Runtime for MyCustomRuntime { /// # type JoinError = MyCustomJoinError; /// # type JoinHandle = MyCustomJoinHandle; /// # /// # fn spawn(fut: F) -> Self::JoinHandle /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # } /// # /// # impl ContextExt for MyCustomRuntime { /// # fn scope(locals: TaskLocals, fut: F) -> Pin + Send>> /// # where /// # F: Future + Send + 'static /// # { /// # unreachable!() /// # } /// # fn get_task_locals() -> Option { /// # unreachable!() /// # } /// # } /// /// use pyo3::prelude::*; /// use futures::{StreamExt, TryStreamExt}; /// /// const TEST_MOD: &str = r#" /// import asyncio /// /// async def gen(): /// for i in range(10): /// await asyncio.sleep(0.1) /// yield i /// "#; /// /// # async fn test_async_gen() -> PyResult<()> { /// let stream = Python::with_gil(|py| { /// let test_mod = PyModule::from_code_bound( /// py, /// TEST_MOD, /// "test_rust_coroutine/test_mod.py", /// "test_mod", /// )?; /// /// pyo3_async_runtimes::generic::into_stream_v2::(test_mod.call_method0("gen")?) /// })?; /// /// let vals = stream /// .map(|item| Python::with_gil(|py| -> PyResult { Ok(item.bind(py).extract()?) })) /// .try_collect::>() /// .await?; /// /// assert_eq!((0..10).collect::>(), vals); /// /// Ok(()) /// # } /// ``` #[cfg(feature = "unstable-streams")] pub fn into_stream_v2( gen: Bound<'_, PyAny>, ) -> PyResult + 'static> where R: Runtime + ContextExt, { into_stream_with_locals_v2::(get_current_locals::(gen.py())?, gen) } pyo3-async-runtimes-0.22.0/src/lib.rs000064400000000000000000000606761046102023000155000ustar 00000000000000#![warn(missing_docs)] #![allow(clippy::borrow_deref_ref)] //! Rust Bindings to the Python Asyncio Event Loop //! //! # Motivation //! //! This crate aims to provide a convenient interface to manage the interop between Python and //! Rust's async/await models. It supports conversions between Rust and Python futures and manages //! the event loops for both languages. Python's threading model and GIL can make this interop a bit //! trickier than one might expect, so there are a few caveats that users should be aware of. //! //! ## Why Two Event Loops //! //! Currently, we don't have a way to run Rust futures directly on Python's event loop. Likewise, //! Python's coroutines cannot be directly spawned on a Rust event loop. The two coroutine models //! require some additional assistance from their event loops, so in all likelihood they will need //! a new _unique_ event loop that addresses the needs of both languages if the coroutines are to //! be run on the same loop. //! //! It's not immediately clear that this would provide worthwhile performance wins either, so in the //! interest of getting something simple out there to facilitate these conversions, this crate //! handles the communication between _separate_ Python and Rust event loops. //! //! ## Python's Event Loop and the Main Thread //! //! Python is very picky about the threads used by the `asyncio` executor. In particular, it needs //! to have control over the main thread in order to handle signals like CTRL-C correctly. This //! means that Cargo's default test harness will no longer work since it doesn't provide a method of //! overriding the main function to add our event loop initialization and finalization. //! //! ## Event Loop References and ContextVars //! //! One problem that arises when interacting with Python's asyncio library is that the functions we //! use to get a reference to the Python event loop can only be called in certain contexts. Since //! PyO3 Asyncio needs to interact with Python's event loop during conversions, the context of these //! conversions can matter a lot. //! //! Likewise, Python's `contextvars` library can require some special treatment. Python functions //! and coroutines can rely on the context of outer coroutines to function correctly, so this //! library needs to be able to preserve `contextvars` during conversions. //! //! > The core conversions we've mentioned so far in the README should insulate you from these //! > concerns in most cases. For the edge cases where they don't, this section should provide you //! > with the information you need to solve these problems. //! //! ### The Main Dilemma //! //! Python programs can have many independent event loop instances throughout the lifetime of the //! application (`asyncio.run` for example creates its own event loop each time it's called for //! instance), and they can even run concurrent with other event loops. For this reason, the most //! correct method of obtaining a reference to the Python event loop is via //! `asyncio.get_running_loop`. //! //! `asyncio.get_running_loop` returns the event loop associated with the current OS thread. It can //! be used inside Python coroutines to spawn concurrent tasks, interact with timers, or in our case //! signal between Rust and Python. This is all well and good when we are operating on a Python //! thread, but since Rust threads are not associated with a Python event loop, //! `asyncio.get_running_loop` will fail when called on a Rust runtime. //! //! `contextvars` operates in a similar way, though the current context is not always associated //! with the current OS thread. Different contexts can be associated with different coroutines even //! if they run on the same OS thread. //! //! ### The Solution //! //! A really straightforward way of dealing with this problem is to pass references to the //! associated Python event loop and context for every conversion. That's why we have a structure //! called `TaskLocals` and a set of conversions that accept it. //! //! `TaskLocals` stores the current event loop, and allows the user to copy the current Python //! context if necessary. The following conversions will use these references to perform the //! necessary conversions and restore Python context when needed: //! //! - `pyo3_async_runtimes::into_future_with_locals` - Convert a Python awaitable into a Rust future. //! - `pyo3_async_runtimes::::future_into_py_with_locals` - Convert a Rust future into a Python //! awaitable. //! - `pyo3_async_runtimes::::local_future_into_py_with_locals` - Convert a `!Send` Rust future //! into a Python awaitable. //! //! One clear disadvantage to this approach is that the Rust application has to explicitly track //! these references. In native libraries, we can't make any assumptions about the underlying event //! loop, so the only reliable way to make sure our conversions work properly is to store these //! references at the callsite to use later on. //! //! ```rust //! use pyo3::{wrap_pyfunction, prelude::*}; //! //! # #[cfg(feature = "tokio-runtime")] //! #[pyfunction] //! fn sleep(py: Python) -> PyResult> { //! // Construct the task locals structure with the current running loop and context //! let locals = pyo3_async_runtimes::TaskLocals::with_running_loop(py)?.copy_context(py)?; //! //! // Convert the async move { } block to a Python awaitable //! pyo3_async_runtimes::tokio::future_into_py_with_locals(py, locals.clone_ref(py), async move { //! let py_sleep = Python::with_gil(|py| { //! // Sometimes we need to call other async Python functions within //! // this future. In order for this to work, we need to track the //! // event loop from earlier. //! pyo3_async_runtimes::into_future_with_locals( //! &locals, //! py.import_bound("asyncio")?.call_method1("sleep", (1,))? //! ) //! })?; //! //! py_sleep.await?; //! //! Ok(()) //! }) //! } //! //! # #[cfg(feature = "tokio-runtime")] //! #[pymodule] //! fn my_mod(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { //! m.add_function(wrap_pyfunction!(sleep, m)?)?; //! Ok(()) //! } //! ``` //! //! > A naive solution to this tracking problem would be to cache a global reference to the asyncio //! > event loop that all PyO3 Asyncio conversions can use. In fact this is what we did in PyO3 //! > Asyncio `v0.13`. This works well for applications, but it soon became clear that this is not //! > so ideal for libraries. Libraries usually have no direct control over how the event loop is //! > managed, they're just expected to work with any event loop at any point in the application. //! > This problem is compounded further when multiple event loops are used in the application since //! > the global reference will only point to one. //! //! Another disadvantage to this explicit approach that is less obvious is that we can no longer //! call our `#[pyfunction] fn sleep` on a Rust runtime since `asyncio.get_running_loop` only works //! on Python threads! It's clear that we need a slightly more flexible approach. //! //! In order to detect the Python event loop at the callsite, we need something like //! `asyncio.get_running_loop` and `contextvars.copy_context` that works for _both Python and Rust_. //! In Python, `asyncio.get_running_loop` uses thread-local data to retrieve the event loop //! associated with the current thread. What we need in Rust is something that can retrieve the //! Python event loop and contextvars associated with the current Rust _task_. //! //! Enter `pyo3_async_runtimes::::get_current_locals`. This function first checks task-local data //! for the `TaskLocals`, then falls back on `asyncio.get_running_loop` and //! `contextvars.copy_context` if no task locals are found. This way both bases are //! covered. //! //! Now, all we need is a way to store the `TaskLocals` for the Rust future. Since this is a //! runtime-specific feature, you can find the following functions in each runtime module: //! //! - `pyo3_async_runtimes::::scope` - Store the task-local data when executing the given Future. //! - `pyo3_async_runtimes::::scope_local` - Store the task-local data when executing the given //! `!Send` Future. //! //! With these new functions, we can make our previous example more correct: //! //! ```rust no_run //! use pyo3::prelude::*; //! //! # #[cfg(feature = "tokio-runtime")] //! #[pyfunction] //! fn sleep(py: Python) -> PyResult> { //! // get the current event loop through task-local data //! // OR `asyncio.get_running_loop` and `contextvars.copy_context` //! let locals = pyo3_async_runtimes::tokio::get_current_locals(py)?; //! //! pyo3_async_runtimes::tokio::future_into_py_with_locals( //! py, //! locals.clone_ref(py), //! // Store the current locals in task-local data //! pyo3_async_runtimes::tokio::scope(locals.clone_ref(py), async move { //! let py_sleep = Python::with_gil(|py| { //! pyo3_async_runtimes::into_future_with_locals( //! // Now we can get the current locals through task-local data //! &pyo3_async_runtimes::tokio::get_current_locals(py)?, //! py.import_bound("asyncio")?.call_method1("sleep", (1,))? //! ) //! })?; //! //! py_sleep.await?; //! //! Ok(Python::with_gil(|py| py.None())) //! }) //! ) //! } //! //! # #[cfg(feature = "tokio-runtime")] //! #[pyfunction] //! fn wrap_sleep(py: Python) -> PyResult> { //! // get the current event loop through task-local data //! // OR `asyncio.get_running_loop` and `contextvars.copy_context` //! let locals = pyo3_async_runtimes::tokio::get_current_locals(py)?; //! //! pyo3_async_runtimes::tokio::future_into_py_with_locals( //! py, //! locals.clone_ref(py), //! // Store the current locals in task-local data //! pyo3_async_runtimes::tokio::scope(locals.clone_ref(py), async move { //! let py_sleep = Python::with_gil(|py| { //! pyo3_async_runtimes::into_future_with_locals( //! &pyo3_async_runtimes::tokio::get_current_locals(py)?, //! // We can also call sleep within a Rust task since the //! // locals are stored in task local data //! sleep(py)? //! ) //! })?; //! //! py_sleep.await?; //! //! Ok(Python::with_gil(|py| py.None())) //! }) //! ) //! } //! //! # #[cfg(feature = "tokio-runtime")] //! #[pymodule] //! fn my_mod(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { //! m.add_function(wrap_pyfunction!(sleep, m)?)?; //! m.add_function(wrap_pyfunction!(wrap_sleep, m)?)?; //! Ok(()) //! } //! ``` //! //! Even though this is more correct, it's clearly not more ergonomic. That's why we introduced a //! set of functions with this functionality baked in: //! //! - `pyo3_async_runtimes::::into_future` //! > Convert a Python awaitable into a Rust future (using //! > `pyo3_async_runtimes::::get_current_locals`) //! - `pyo3_async_runtimes::::future_into_py` //! > Convert a Rust future into a Python awaitable (using //! > `pyo3_async_runtimes::::get_current_locals` and `pyo3_async_runtimes::::scope` to set the //! > task-local event loop for the given Rust future) //! - `pyo3_async_runtimes::::local_future_into_py` //! > Convert a `!Send` Rust future into a Python awaitable (using //! > `pyo3_async_runtimes::::get_current_locals` and `pyo3_async_runtimes::::scope_local` to //! > set the task-local event loop for the given Rust future). //! //! __These are the functions that we recommend using__. With these functions, the previous example //! can be rewritten to be more compact: //! //! ```rust //! use pyo3::prelude::*; //! //! # #[cfg(feature = "tokio-runtime")] //! #[pyfunction] //! fn sleep(py: Python) -> PyResult> { //! pyo3_async_runtimes::tokio::future_into_py(py, async move { //! let py_sleep = Python::with_gil(|py| { //! pyo3_async_runtimes::tokio::into_future( //! py.import_bound("asyncio")?.call_method1("sleep", (1,))? //! ) //! })?; //! //! py_sleep.await?; //! //! Ok(Python::with_gil(|py| py.None())) //! }) //! } //! //! # #[cfg(feature = "tokio-runtime")] //! #[pyfunction] //! fn wrap_sleep(py: Python) -> PyResult> { //! pyo3_async_runtimes::tokio::future_into_py(py, async move { //! let py_sleep = Python::with_gil(|py| { //! pyo3_async_runtimes::tokio::into_future(sleep(py)?) //! })?; //! //! py_sleep.await?; //! //! Ok(Python::with_gil(|py| py.None())) //! }) //! } //! //! # #[cfg(feature = "tokio-runtime")] //! #[pymodule] //! fn my_mod(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { //! m.add_function(wrap_pyfunction!(sleep, m)?)?; //! m.add_function(wrap_pyfunction!(wrap_sleep, m)?)?; //! Ok(()) //! } //! ``` //! //! > A special thanks to [@ShadowJonathan](https://github.com/ShadowJonathan) for helping with the //! > design and review of these changes! //! //! ## Rust's Event Loop //! //! Currently only the Async-Std and Tokio runtimes are supported by this crate. If you need support //! for another runtime, feel free to make a request on GitHub (or attempt to add support yourself //! with the [`generic`] module)! //! //! > _In the future, we may implement first class support for more Rust runtimes. Contributions are //! > welcome as well!_ //! //! ## Features //! //! Items marked with //! attributes //! > are only available when the `attributes` Cargo feature is enabled: //! //! ```toml //! [dependencies.pyo3-async-runtimes] //! version = "0.22" //! features = ["attributes"] //! ``` //! //! Items marked with //! async-std-runtime //! > are only available when the `async-std-runtime` Cargo feature is enabled: //! //! ```toml //! [dependencies.pyo3-async-runtimes] //! version = "0.22" //! features = ["async-std-runtime"] //! ``` //! //! Items marked with //! tokio-runtime //! > are only available when the `tokio-runtime` Cargo feature is enabled: //! //! ```toml //! [dependencies.pyo3-async-runtimes] //! version = "0.22" //! features = ["tokio-runtime"] //! ``` //! //! Items marked with //! testing //! > are only available when the `testing` Cargo feature is enabled: //! //! ```toml //! [dependencies.pyo3-async-runtimes] //! version = "0.22" //! features = ["testing"] //! ``` /// Re-exported for #[test] attributes #[cfg(all(feature = "attributes", feature = "testing"))] pub use inventory; /// testing Utilities for writing PyO3 Asyncio tests #[cfg(feature = "testing")] pub mod testing; #[cfg(feature = "async-std")] pub mod async_std; #[cfg(feature = "tokio-runtime")] pub mod tokio; /// Errors and exceptions related to PyO3 Asyncio pub mod err; pub mod generic; #[pymodule] fn pyo3_async_runtimes(py: Python, m: &Bound) -> PyResult<()> { m.add("RustPanic", py.get_type_bound::())?; Ok(()) } /// Test README #[doc(hidden)] pub mod doc_test { #[allow(unused)] macro_rules! doc_comment { ($x:expr, $module:item) => { #[doc = $x] $module }; } #[allow(unused)] macro_rules! doctest { ($x:expr, $y:ident) => { doc_comment!(include_str!($x), mod $y {}); }; } #[cfg(all( feature = "async-std-runtime", feature = "tokio-runtime", feature = "attributes" ))] doctest!("../README.md", readme_md); } use std::future::Future; use futures::channel::oneshot; use once_cell::sync::OnceCell; use pyo3::{ prelude::*, types::{PyDict, PyTuple}, }; static ASYNCIO: OnceCell = OnceCell::new(); static CONTEXTVARS: OnceCell = OnceCell::new(); static ENSURE_FUTURE: OnceCell = OnceCell::new(); static GET_RUNNING_LOOP: OnceCell = OnceCell::new(); fn ensure_future<'p>(py: Python<'p>, awaitable: &Bound<'p, PyAny>) -> PyResult> { ENSURE_FUTURE .get_or_try_init(|| -> PyResult { Ok(asyncio(py)?.getattr("ensure_future")?.into()) })? .bind(py) .call1((awaitable,)) } fn create_future(event_loop: Bound) -> PyResult> { event_loop.call_method0("create_future") } fn close(event_loop: Bound) -> PyResult<()> { event_loop.call_method1( "run_until_complete", (event_loop.call_method0("shutdown_asyncgens")?,), )?; // how to do this prior to 3.9? if event_loop.hasattr("shutdown_default_executor")? { event_loop.call_method1( "run_until_complete", (event_loop.call_method0("shutdown_default_executor")?,), )?; } event_loop.call_method0("close")?; Ok(()) } fn asyncio(py: Python) -> PyResult<&Bound> { ASYNCIO .get_or_try_init(|| Ok(py.import_bound("asyncio")?.into())) .map(|asyncio| asyncio.bind(py)) } /// Get a reference to the Python Event Loop from Rust /// /// Equivalent to `asyncio.get_running_loop()` in Python 3.7+. pub fn get_running_loop(py: Python) -> PyResult> { // Ideally should call get_running_loop, but calls get_event_loop for compatibility when // get_running_loop is not available. GET_RUNNING_LOOP .get_or_try_init(|| -> PyResult { let asyncio = asyncio(py)?; Ok(asyncio.getattr("get_running_loop")?.into()) })? .bind(py) .call0() } fn contextvars(py: Python) -> PyResult<&Bound> { Ok(CONTEXTVARS .get_or_try_init(|| py.import_bound("contextvars").map(|m| m.into()))? .bind(py)) } fn copy_context(py: Python) -> PyResult> { contextvars(py)?.call_method0("copy_context") } /// Task-local data to store for Python conversions. #[derive(Debug)] pub struct TaskLocals { /// Track the event loop of the Python task event_loop: PyObject, /// Track the contextvars of the Python task context: PyObject, } impl TaskLocals { /// At a minimum, TaskLocals must store the event loop. pub fn new(event_loop: Bound) -> Self { Self { context: event_loop.py().None(), event_loop: event_loop.into(), } } /// Construct TaskLocals with the event loop returned by `get_running_loop` pub fn with_running_loop(py: Python) -> PyResult { Ok(Self::new(get_running_loop(py)?)) } /// Manually provide the contextvars for the current task. pub fn with_context(self, context: Bound) -> Self { Self { context: context.into(), ..self } } /// Capture the current task's contextvars pub fn copy_context(self, py: Python) -> PyResult { Ok(self.with_context(copy_context(py)?)) } /// Get a reference to the event loop pub fn event_loop<'p>(&self, py: Python<'p>) -> Bound<'p, PyAny> { self.event_loop.clone_ref(py).into_bound(py) } /// Get a reference to the python context pub fn context<'p>(&self, py: Python<'p>) -> Bound<'p, PyAny> { self.context.clone_ref(py).into_bound(py) } /// Create a clone of the TaskLocals by incrementing the reference counters of the event loop and /// contextvars. pub fn clone_ref(&self, py: Python<'_>) -> Self { Self { event_loop: self.event_loop.clone_ref(py), context: self.context.clone_ref(py), } } } #[pyclass] struct PyTaskCompleter { tx: Option>>, } #[pymethods] impl PyTaskCompleter { #[pyo3(signature = (task))] pub fn __call__(&mut self, task: &Bound) -> PyResult<()> { debug_assert!(task.call_method0("done")?.extract()?); let result = match task.call_method0("result") { Ok(val) => Ok(val.into()), Err(e) => Err(e), }; // unclear to me whether or not this should be a panic or silent error. // // calling PyTaskCompleter twice should not be possible, but I don't think it really hurts // anything if it happens. if let Some(tx) = self.tx.take() { if tx.send(result).is_err() { // cancellation is not an error } } Ok(()) } } #[pyclass] struct PyEnsureFuture { awaitable: PyObject, tx: Option>>, } #[pymethods] impl PyEnsureFuture { pub fn __call__(&mut self) -> PyResult<()> { Python::with_gil(|py| { let task = ensure_future(py, self.awaitable.bind(py))?; let on_complete = PyTaskCompleter { tx: self.tx.take() }; task.call_method1("add_done_callback", (on_complete,))?; Ok(()) }) } } fn call_soon_threadsafe( event_loop: &Bound, context: &Bound, args: impl IntoPy>, ) -> PyResult<()> { let py = event_loop.py(); let kwargs = PyDict::new_bound(py); kwargs.set_item("context", context)?; event_loop.call_method("call_soon_threadsafe", args, Some(&kwargs))?; Ok(()) } /// Convert a Python `awaitable` into a Rust Future /// /// This function converts the `awaitable` into a Python Task using `run_coroutine_threadsafe`. A /// completion handler sends the result of this Task through a /// `futures::channel::oneshot::Sender>` and the future returned by this function /// simply awaits the result through the `futures::channel::oneshot::Receiver>`. /// /// # Arguments /// * `locals` - The Python event loop and context to be used for the provided awaitable /// * `awaitable` - The Python `awaitable` to be converted /// /// # Examples /// /// ``` /// use std::time::Duration; /// /// use pyo3::prelude::*; /// /// const PYTHON_CODE: &'static str = r#" /// import asyncio /// /// async def py_sleep(duration): /// await asyncio.sleep(duration) /// "#; /// /// # #[cfg(feature = "tokio-runtime")] /// async fn py_sleep(seconds: f32) -> PyResult<()> { /// let test_mod = Python::with_gil(|py| -> PyResult { /// Ok( /// PyModule::from_code_bound( /// py, /// PYTHON_CODE, /// "test_into_future/test_mod.py", /// "test_mod" /// )? /// .into() /// ) /// })?; /// /// Python::with_gil(|py| { /// pyo3_async_runtimes::into_future_with_locals( /// &pyo3_async_runtimes::tokio::get_current_locals(py)?, /// test_mod /// .call_method1(py, "py_sleep", (seconds.into_py(py),))? /// .into_bound(py), /// ) /// })? /// .await?; /// Ok(()) /// } /// ``` pub fn into_future_with_locals( locals: &TaskLocals, awaitable: Bound, ) -> PyResult> + Send> { let py = awaitable.py(); let (tx, rx) = oneshot::channel(); call_soon_threadsafe( &locals.event_loop(py), &locals.context(py), (PyEnsureFuture { awaitable: awaitable.into(), tx: Some(tx), },), )?; Ok(async move { match rx.await { Ok(item) => item, Err(_) => Python::with_gil(|py| { Err(PyErr::from_value_bound( asyncio(py)?.call_method0("CancelledError")?, )) }), } }) } fn dump_err(py: Python<'_>) -> impl FnOnce(PyErr) + '_ { move |e| { // We can't display Python exceptions via std::fmt::Display, // so print the error here manually. e.print_and_set_sys_last_vars(py); } } pyo3-async-runtimes-0.22.0/src/testing.rs000064400000000000000000000275751046102023000164100ustar 00000000000000//! # PyO3 Asyncio Testing Utilities //! //! This module provides some utilities for parsing test arguments as well as running and filtering //! a sequence of tests. //! //! As mentioned [here](crate#pythons-event-loop), PyO3 Asyncio tests cannot use the default test //! harness since it doesn't allow Python to gain control over the main thread. Instead, we have to //! provide our own test harness in order to create integration tests. //! //! Running `pyo3-async-runtimes` code in doc tests _is_ supported however since each doc test has its own //! `main` function. When writing doc tests, you may use the //! [`#[pyo3_async_runtimes::async_std::main]`](crate::async_std::main) or //! [`#[pyo3_async_runtimes::tokio::main]`](crate::tokio::main) macros on the test's main function to run //! your test. //! //! If you don't want to write doc tests, you're unfortunately stuck with integration tests since //! lib tests do not offer the same level of flexibility for the `main` fn. That being said, //! overriding the default test harness can be quite different from what you're used to doing for //! integration tests, so these next sections will walk you through this process. //! //! ## Main Test File //! First, we need to create the test's main file. Although these tests are considered integration //! tests, we cannot put them in the `tests` directory since that is a special directory owned by //! Cargo. Instead, we put our tests in a `pytests` directory. //! //! > The name `pytests` is just a convention. You can name this folder anything you want in your own //! > projects. //! //! We'll also want to provide the test's main function. Most of the functionality that the test harness needs is packed in the [`pyo3_async_runtimes::testing::main`](https://docs.rs/pyo3-async-runtimes/latest/pyo3_async_runtimes/testing/fn.main.html) function. This function will parse the test's CLI arguments, collect and pass the functions marked with [`#[pyo3_async_runtimes::async_std::test]`](https://docs.rs/pyo3-async-runtimes/latest/pyo3_async_runtimes/async_std/attr.test.html) or [`#[pyo3_async_runtimes::tokio::test]`](https://docs.rs/pyo3-async-runtimes/latest/pyo3_async_runtimes/tokio/attr.test.html) and pass them into the test harness for running and filtering. //! //! `pytests/test_example.rs` for the `tokio` runtime: //! ```rust //! # #[cfg(all(feature = "tokio-runtime", feature = "attributes"))] //! #[pyo3_async_runtimes::tokio::main] //! async fn main() -> pyo3::PyResult<()> { //! pyo3_async_runtimes::testing::main().await //! } //! # #[cfg(not(all(feature = "tokio-runtime", feature = "attributes")))] //! # fn main() {} //! ``` //! //! `pytests/test_example.rs` for the `async-std` runtime: //! ```rust //! # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))] //! #[pyo3_async_runtimes::async_std::main] //! async fn main() -> pyo3::PyResult<()> { //! pyo3_async_runtimes::testing::main().await //! } //! # #[cfg(not(all(feature = "async-std-runtime", feature = "attributes")))] //! # fn main() {} //! ``` //! //! ## Cargo Configuration //! Next, we need to add our test file to the Cargo manifest by adding the following section to the //! `Cargo.toml` //! //! ```toml //! [[test]] //! name = "test_example" //! path = "pytests/test_example.rs" //! harness = false //! ``` //! //! Also add the `testing` and `attributes` features to the `pyo3-async-runtimes` dependency and select your preferred runtime: //! //! ```toml //! pyo3-async-runtimes = { version = "0.22", features = ["testing", "attributes", "async-std-runtime"] } //! ``` //! //! At this point, you should be able to run the test via `cargo test` //! //! ### Adding Tests to the PyO3 Asyncio Test Harness //! //! We can add tests anywhere in the test crate with the runtime's corresponding `#[test]` attribute: //! //! For `async-std` use the [`pyo3_async_runtimes::async_std::test`](https://docs.rs/pyo3-async-runtimes/latest/pyo3_async_runtimes/async_std/attr.test.html) attribute: //! ```rust //! # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))] //! mod tests { //! use std::{time::Duration, thread}; //! //! use pyo3::prelude::*; //! //! // tests can be async //! #[pyo3_async_runtimes::async_std::test] //! async fn test_async_sleep() -> PyResult<()> { //! async_std::task::sleep(Duration::from_secs(1)).await; //! Ok(()) //! } //! //! // they can also be synchronous //! #[pyo3_async_runtimes::async_std::test] //! fn test_blocking_sleep() -> PyResult<()> { //! thread::sleep(Duration::from_secs(1)); //! Ok(()) //! } //! } //! //! # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))] //! #[pyo3_async_runtimes::async_std::main] //! async fn main() -> pyo3::PyResult<()> { //! pyo3_async_runtimes::testing::main().await //! } //! # #[cfg(not(all(feature = "async-std-runtime", feature = "attributes")))] //! # fn main() {} //! ``` //! //! For `tokio` use the [`pyo3_async_runtimes::tokio::test`](https://docs.rs/pyo3-async-runtimes/latest/pyo3_async_runtimes/tokio/attr.test.html) attribute: //! ```rust //! # #[cfg(all(feature = "tokio-runtime", feature = "attributes"))] //! mod tests { //! use std::{time::Duration, thread}; //! //! use pyo3::prelude::*; //! //! // tests can be async //! #[pyo3_async_runtimes::tokio::test] //! async fn test_async_sleep() -> PyResult<()> { //! tokio::time::sleep(Duration::from_secs(1)).await; //! Ok(()) //! } //! //! // they can also be synchronous //! #[pyo3_async_runtimes::tokio::test] //! fn test_blocking_sleep() -> PyResult<()> { //! thread::sleep(Duration::from_secs(1)); //! Ok(()) //! } //! } //! //! # #[cfg(all(feature = "tokio-runtime", feature = "attributes"))] //! #[pyo3_async_runtimes::tokio::main] //! async fn main() -> pyo3::PyResult<()> { //! pyo3_async_runtimes::testing::main().await //! } //! # #[cfg(not(all(feature = "tokio-runtime", feature = "attributes")))] //! # fn main() {} //! ``` //! //! ## Lib Tests //! //! Unfortunately, as we mentioned at the beginning, these utilities will only run in integration //! tests and doc tests. Running lib tests are out of the question since we need control over the //! main function. You can however perform compilation checks for lib tests. This is much more //! useful in doc tests than it is for lib tests, but the option is there if you want it. //! //! `my-crate/src/lib.rs` //! ``` //! # #[cfg(all( //! # any(feature = "async-std-runtime", feature = "tokio-runtime"), //! # feature = "attributes" //! # ))] //! mod tests { //! use pyo3::prelude::*; //! //! # #[cfg(feature = "async-std-runtime")] //! #[pyo3_async_runtimes::async_std::test] //! async fn test_async_std_async_test_compiles() -> PyResult<()> { //! Ok(()) //! } //! # #[cfg(feature = "async-std-runtime")] //! #[pyo3_async_runtimes::async_std::test] //! fn test_async_std_sync_test_compiles() -> PyResult<()> { //! Ok(()) //! } //! //! # #[cfg(feature = "tokio-runtime")] //! #[pyo3_async_runtimes::tokio::test] //! async fn test_tokio_async_test_compiles() -> PyResult<()> { //! Ok(()) //! } //! # #[cfg(feature = "tokio-runtime")] //! #[pyo3_async_runtimes::tokio::test] //! fn test_tokio_sync_test_compiles() -> PyResult<()> { //! Ok(()) //! } //! } //! //! # fn main() {} //! ``` use std::{future::Future, pin::Pin}; use clap::{Arg, Command}; use futures::stream::{self, StreamExt}; use pyo3::prelude::*; /// Args that should be provided to the test program /// /// These args are meant to mirror the default test harness's args. /// > Currently only `--filter` is supported. #[derive(Default)] pub struct Args { filter: Option, } /// Parse the test args from the command line /// /// This should be called at the start of your test harness to give the CLI some /// control over how our tests are run. /// /// Ideally, we should mirror the default test harness's arguments exactly, but /// for the sake of simplicity, only filtering is supported for now. If you want /// more features, feel free to request them /// [here](https://github.com/PyO3/pyo3-async-runtimes/issues). /// /// # Examples /// /// Running the following function: /// ``` /// # use pyo3_async_runtimes::testing::parse_args; /// let args = parse_args(); /// ``` /// /// Produces the following usage string: /// /// ```bash /// Pyo3 Asyncio Test Suite /// USAGE: /// test_example [TESTNAME] /// /// FLAGS: /// -h, --help Prints help information /// -V, --version Prints version information /// /// ARGS: /// If specified, only run tests containing this string in their names /// ``` pub fn parse_args() -> Args { let matches = Command::new("PyO3 Asyncio Test Suite") .arg( Arg::new("TESTNAME") .help("If specified, only run tests containing this string in their names"), ) .get_matches(); Args { filter: matches.get_one::("TESTNAME").cloned(), } } type TestFn = dyn Fn() -> Pin> + Send>> + Send + Sync; /// The structure used by the `#[test]` macros to provide a test to the `pyo3-async-runtimes` test harness. #[derive(Clone)] pub struct Test { /// The fully qualified name of the test pub name: &'static str, /// The function used to create the task that runs the test. pub test_fn: &'static TestFn, } impl Test { /// Create the task that runs the test pub fn task( &self, ) -> std::pin::Pin> + Send>> { (self.test_fn)() } } inventory::collect!(Test); /// Run a sequence of tests while applying any necessary filtering from the `Args` pub async fn test_harness(tests: Vec, args: Args) -> PyResult<()> { stream::iter(tests) .for_each_concurrent(Some(4), |test| { let mut ignore = false; if let Some(filter) = args.filter.as_ref() { if !test.name.contains(filter) { ignore = true; } } async move { if !ignore { test.task().await.unwrap(); println!("test {} ... ok", test.name); } } }) .await; Ok(()) } /// Parses test arguments and passes the tests to the `pyo3-async-runtimes` test harness /// /// This function collects the test structures from the `inventory` boilerplate and forwards them to /// the test harness. /// /// # Examples /// /// ``` /// # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))] /// use pyo3::prelude::*; /// /// # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))] /// #[pyo3_async_runtimes::async_std::main] /// async fn main() -> PyResult<()> { /// pyo3_async_runtimes::testing::main().await /// } /// # #[cfg(not(all(feature = "async-std-runtime", feature = "attributes")))] /// # fn main() { } /// ``` pub async fn main() -> PyResult<()> { let args = parse_args(); test_harness(inventory::iter::().cloned().collect(), args).await } #[cfg(test)] #[cfg(all( feature = "testing", feature = "attributes", any(feature = "async-std-runtime", feature = "tokio-runtime") ))] mod tests { use pyo3::prelude::*; use crate as pyo3_async_runtimes; #[cfg(feature = "async-std-runtime")] #[pyo3_async_runtimes::async_std::test] async fn test_async_std_async_test_compiles() -> PyResult<()> { Ok(()) } #[cfg(feature = "async-std-runtime")] #[pyo3_async_runtimes::async_std::test] fn test_async_std_sync_test_compiles() -> PyResult<()> { Ok(()) } #[cfg(feature = "tokio-runtime")] #[pyo3_async_runtimes::tokio::test] async fn test_tokio_async_test_compiles() -> PyResult<()> { Ok(()) } #[cfg(feature = "tokio-runtime")] #[pyo3_async_runtimes::tokio::test] fn test_tokio_sync_test_compiles() -> PyResult<()> { Ok(()) } } pyo3-async-runtimes-0.22.0/src/tokio.rs000064400000000000000000000702441046102023000160470ustar 00000000000000//! tokio-runtime PyO3 Asyncio functions specific to the tokio runtime //! //! Items marked with //! unstable-streams //! >are only available when the `unstable-streams` Cargo feature is enabled: //! //! ```toml //! [dependencies.pyo3-async-runtimes] //! version = "0.22" //! features = ["unstable-streams"] //! ``` use std::ops::Deref; use std::{future::Future, pin::Pin, sync::Mutex}; use ::tokio::{ runtime::{Builder, Runtime}, task, }; use once_cell::{ sync::{Lazy, OnceCell}, unsync::OnceCell as UnsyncOnceCell, }; use pyo3::prelude::*; use crate::{ generic::{self, ContextExt, LocalContextExt, Runtime as GenericRuntime, SpawnLocalExt}, TaskLocals, }; /// attributes /// re-exports for macros #[cfg(feature = "attributes")] pub mod re_exports { /// re-export pending to be used in tokio macros without additional dependency pub use futures::future::pending; /// re-export tokio::runtime to build runtimes in tokio macros without additional dependency pub use tokio::runtime; } /// attributes #[cfg(feature = "attributes")] pub use pyo3_async_runtimes_macros::tokio_main as main; /// attributes /// testing /// Registers a `tokio` test with the `pyo3-asyncio` test harness #[cfg(all(feature = "attributes", feature = "testing"))] pub use pyo3_async_runtimes_macros::tokio_test as test; enum Pyo3Runtime { Borrowed(&'static Runtime), Owned(Runtime), } impl Deref for Pyo3Runtime { type Target = Runtime; fn deref(&self) -> &Self::Target { match self { Self::Borrowed(rt) => rt, Self::Owned(rt) => rt, } } } static TOKIO_BUILDER: Lazy> = Lazy::new(|| Mutex::new(multi_thread())); static TOKIO_RUNTIME: OnceCell = OnceCell::new(); impl generic::JoinError for task::JoinError { fn is_panic(&self) -> bool { task::JoinError::is_panic(self) } fn into_panic(self) -> Box { task::JoinError::into_panic(self) } } struct TokioRuntime; tokio::task_local! { static TASK_LOCALS: UnsyncOnceCell; } impl GenericRuntime for TokioRuntime { type JoinError = task::JoinError; type JoinHandle = task::JoinHandle<()>; fn spawn(fut: F) -> Self::JoinHandle where F: Future + Send + 'static, { get_runtime().spawn(async move { fut.await; }) } } impl ContextExt for TokioRuntime { fn scope(locals: TaskLocals, fut: F) -> Pin + Send>> where F: Future + Send + 'static, { let cell = UnsyncOnceCell::new(); cell.set(locals).unwrap(); Box::pin(TASK_LOCALS.scope(cell, fut)) } fn get_task_locals() -> Option { TASK_LOCALS .try_with(|c| { c.get() .map(|locals| Python::with_gil(|py| locals.clone_ref(py))) }) .unwrap_or_default() } } impl SpawnLocalExt for TokioRuntime { fn spawn_local(fut: F) -> Self::JoinHandle where F: Future + 'static, { tokio::task::spawn_local(fut) } } impl LocalContextExt for TokioRuntime { fn scope_local(locals: TaskLocals, fut: F) -> Pin>> where F: Future + 'static, { let cell = UnsyncOnceCell::new(); cell.set(locals).unwrap(); Box::pin(TASK_LOCALS.scope(cell, fut)) } } /// Set the task local event loop for the given future pub async fn scope(locals: TaskLocals, fut: F) -> R where F: Future + Send + 'static, { TokioRuntime::scope(locals, fut).await } /// Set the task local event loop for the given !Send future pub async fn scope_local(locals: TaskLocals, fut: F) -> R where F: Future + 'static, { TokioRuntime::scope_local(locals, fut).await } /// Get the current event loop from either Python or Rust async task local context /// /// This function first checks if the runtime has a task-local reference to the Python event loop. /// If not, it calls [`get_running_loop`](`crate::get_running_loop`) to get the event loop /// associated with the current OS thread. pub fn get_current_loop(py: Python) -> PyResult> { generic::get_current_loop::(py) } /// Either copy the task locals from the current task OR get the current running loop and /// contextvars from Python. pub fn get_current_locals(py: Python) -> PyResult { generic::get_current_locals::(py) } /// Initialize the Tokio runtime with a custom build pub fn init(builder: Builder) { *TOKIO_BUILDER.lock().unwrap() = builder } /// Initialize the Tokio runtime with a custom Tokio runtime /// /// Returns Ok(()) if success and Err(()) if it had been inited. #[allow(clippy::result_unit_err)] pub fn init_with_runtime(runtime: &'static Runtime) -> Result<(), ()> { TOKIO_RUNTIME .set(Pyo3Runtime::Borrowed(runtime)) .map_err(|_| ()) } /// Get a reference to the current tokio runtime pub fn get_runtime<'a>() -> &'a Runtime { TOKIO_RUNTIME.get_or_init(|| { let rt = TOKIO_BUILDER .lock() .unwrap() .build() .expect("Unable to build Tokio runtime"); Pyo3Runtime::Owned(rt) }) } fn multi_thread() -> Builder { let mut builder = Builder::new_multi_thread(); builder.enable_all(); builder } /// Run the event loop until the given Future completes /// /// The event loop runs until the given future is complete. /// /// After this function returns, the event loop can be resumed with [`run_until_complete`] /// /// # Arguments /// * `event_loop` - The Python event loop that should run the future /// * `fut` - The future to drive to completion /// /// # Examples /// /// ``` /// # use std::time::Duration; /// # /// # use pyo3::prelude::*; /// # /// # pyo3::prepare_freethreaded_python(); /// # Python::with_gil(|py| -> PyResult<()> { /// # let event_loop = py.import_bound("asyncio")?.call_method0("new_event_loop")?; /// pyo3_async_runtimes::tokio::run_until_complete(event_loop, async move { /// tokio::time::sleep(Duration::from_secs(1)).await; /// Ok(()) /// })?; /// # Ok(()) /// # }).unwrap(); /// ``` pub fn run_until_complete(event_loop: Bound, fut: F) -> PyResult where F: Future> + Send + 'static, T: Send + Sync + 'static, { generic::run_until_complete::(&event_loop, fut) } /// Run the event loop until the given Future completes /// /// # Arguments /// * `py` - The current PyO3 GIL guard /// * `fut` - The future to drive to completion /// /// # Examples /// /// ```no_run /// # use std::time::Duration; /// # /// # use pyo3::prelude::*; /// # /// fn main() { /// Python::with_gil(|py| { /// pyo3_async_runtimes::tokio::run(py, async move { /// tokio::time::sleep(Duration::from_secs(1)).await; /// Ok(()) /// }) /// .map_err(|e| { /// e.print_and_set_sys_last_vars(py); /// }) /// .unwrap(); /// }) /// } /// ``` pub fn run(py: Python, fut: F) -> PyResult where F: Future> + Send + 'static, T: Send + Sync + 'static, { generic::run::(py, fut) } /// Convert a Rust Future into a Python awaitable /// /// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`, /// the Rust future will be cancelled as well (new behaviour in `v0.15`). /// /// Python `contextvars` are preserved when calling async Python functions within the Rust future /// via [`into_future`] (new behaviour in `v0.15`). /// /// > Although `contextvars` are preserved for async Python functions, synchronous functions will /// > unfortunately fail to resolve them when called within the Rust future. This is because the /// > function is being called from a Rust thread, not inside an actual Python coroutine context. /// > /// > As a workaround, you can get the `contextvars` from the current task locals using /// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your /// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the /// > synchronous function, and restore the previous context when it returns or raises an exception. /// /// # Arguments /// * `py` - PyO3 GIL guard /// * `locals` - The task locals for the given future /// * `fut` - The Rust future to be converted /// /// # Examples /// /// ``` /// use std::time::Duration; /// /// use pyo3::prelude::*; /// /// /// Awaitable sleep function /// #[pyfunction] /// fn sleep_for<'p>(py: Python<'p>, secs: Bound<'p, PyAny>) -> PyResult> { /// let secs = secs.extract()?; /// pyo3_async_runtimes::tokio::future_into_py_with_locals( /// py, /// pyo3_async_runtimes::tokio::get_current_locals(py)?, /// async move { /// tokio::time::sleep(Duration::from_secs(secs)).await; /// Python::with_gil(|py| Ok(py.None())) /// } /// ) /// } /// ``` pub fn future_into_py_with_locals( py: Python, locals: TaskLocals, fut: F, ) -> PyResult> where F: Future> + Send + 'static, T: IntoPy, { generic::future_into_py_with_locals::(py, locals, fut) } /// Convert a Rust Future into a Python awaitable /// /// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`, /// the Rust future will be cancelled as well (new behaviour in `v0.15`). /// /// Python `contextvars` are preserved when calling async Python functions within the Rust future /// via [`into_future`] (new behaviour in `v0.15`). /// /// > Although `contextvars` are preserved for async Python functions, synchronous functions will /// > unfortunately fail to resolve them when called within the Rust future. This is because the /// > function is being called from a Rust thread, not inside an actual Python coroutine context. /// > /// > As a workaround, you can get the `contextvars` from the current task locals using /// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your /// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the /// > synchronous function, and restore the previous context when it returns or raises an exception. /// /// # Arguments /// * `py` - The current PyO3 GIL guard /// * `fut` - The Rust future to be converted /// /// # Examples /// /// ``` /// use std::time::Duration; /// /// use pyo3::prelude::*; /// /// /// Awaitable sleep function /// #[pyfunction] /// fn sleep_for<'p>(py: Python<'p>, secs: Bound<'p, PyAny>) -> PyResult> { /// let secs = secs.extract()?; /// pyo3_async_runtimes::tokio::future_into_py(py, async move { /// tokio::time::sleep(Duration::from_secs(secs)).await; /// Ok(()) /// }) /// } /// ``` pub fn future_into_py(py: Python, fut: F) -> PyResult> where F: Future> + Send + 'static, T: IntoPy, { generic::future_into_py::(py, fut) } /// Convert a `!Send` Rust Future into a Python awaitable /// /// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`, /// the Rust future will be cancelled as well (new behaviour in `v0.15`). /// /// Python `contextvars` are preserved when calling async Python functions within the Rust future /// via [`into_future`] (new behaviour in `v0.15`). /// /// > Although `contextvars` are preserved for async Python functions, synchronous functions will /// > unfortunately fail to resolve them when called within the Rust future. This is because the /// > function is being called from a Rust thread, not inside an actual Python coroutine context. /// > /// > As a workaround, you can get the `contextvars` from the current task locals using /// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your /// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the /// > synchronous function, and restore the previous context when it returns or raises an exception. /// /// # Arguments /// * `py` - PyO3 GIL guard /// * `locals` - The task locals for the given future /// * `fut` - The Rust future to be converted /// /// # Examples /// /// ``` /// use std::{rc::Rc, time::Duration}; /// /// use pyo3::prelude::*; /// /// /// Awaitable non-send sleep function /// #[pyfunction] /// fn sleep_for(py: Python, secs: u64) -> PyResult> { /// // Rc is non-send so it cannot be passed into pyo3_async_runtimes::tokio::future_into_py /// let secs = Rc::new(secs); /// /// pyo3_async_runtimes::tokio::local_future_into_py_with_locals( /// py, /// pyo3_async_runtimes::tokio::get_current_locals(py)?, /// async move { /// tokio::time::sleep(Duration::from_secs(*secs)).await; /// Python::with_gil(|py| Ok(py.None())) /// } /// ) /// } /// /// # #[cfg(all(feature = "tokio-runtime", feature = "attributes"))] /// #[pyo3_async_runtimes::tokio::main] /// async fn main() -> PyResult<()> { /// let locals = Python::with_gil(|py| -> PyResult<_> { /// pyo3_async_runtimes::tokio::get_current_locals(py) /// })?; /// /// // the main coroutine is running in a Send context, so we cannot use LocalSet here. Instead /// // we use spawn_blocking in order to use LocalSet::block_on /// tokio::task::spawn_blocking(move || { /// // LocalSet allows us to work with !Send futures within tokio. Without it, any calls to /// // pyo3_async_runtimes::tokio::local_future_into_py will panic. /// tokio::task::LocalSet::new().block_on( /// pyo3_async_runtimes::tokio::get_runtime(), /// pyo3_async_runtimes::tokio::scope_local(locals, async { /// Python::with_gil(|py| { /// let py_future = sleep_for(py, 1)?; /// pyo3_async_runtimes::tokio::into_future(py_future) /// })? /// .await?; /// /// Ok(()) /// }) /// ) /// }).await.unwrap() /// } /// # #[cfg(not(all(feature = "tokio-runtime", feature = "attributes")))] /// # fn main() {} /// ``` #[deprecated( since = "0.18.0", note = "Questionable whether these conversions have real-world utility (see https://github.com/awestlake87/pyo3-asyncio/issues/59#issuecomment-1008038497 and let me know if you disagree!)" )] #[allow(deprecated)] pub fn local_future_into_py_with_locals( py: Python, locals: TaskLocals, fut: F, ) -> PyResult> where F: Future> + 'static, T: IntoPy, { generic::local_future_into_py_with_locals::(py, locals, fut) } /// Convert a `!Send` Rust Future into a Python awaitable /// /// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`, /// the Rust future will be cancelled as well (new behaviour in `v0.15`). /// /// Python `contextvars` are preserved when calling async Python functions within the Rust future /// via [`into_future`] (new behaviour in `v0.15`). /// /// > Although `contextvars` are preserved for async Python functions, synchronous functions will /// > unfortunately fail to resolve them when called within the Rust future. This is because the /// > function is being called from a Rust thread, not inside an actual Python coroutine context. /// > /// > As a workaround, you can get the `contextvars` from the current task locals using /// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your /// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the /// > synchronous function, and restore the previous context when it returns or raises an exception. /// /// # Arguments /// * `py` - The current PyO3 GIL guard /// * `fut` - The Rust future to be converted /// /// # Examples /// /// ``` /// use std::{rc::Rc, time::Duration}; /// /// use pyo3::prelude::*; /// /// /// Awaitable non-send sleep function /// #[pyfunction] /// fn sleep_for(py: Python, secs: u64) -> PyResult> { /// // Rc is non-send so it cannot be passed into pyo3_async_runtimes::tokio::future_into_py /// let secs = Rc::new(secs); /// pyo3_async_runtimes::tokio::local_future_into_py(py, async move { /// tokio::time::sleep(Duration::from_secs(*secs)).await; /// Ok(()) /// }) /// } /// /// # #[cfg(all(feature = "tokio-runtime", feature = "attributes"))] /// #[pyo3_async_runtimes::tokio::main] /// async fn main() -> PyResult<()> { /// let locals = Python::with_gil(|py| { /// pyo3_async_runtimes::tokio::get_current_locals(py).unwrap() /// }); /// /// // the main coroutine is running in a Send context, so we cannot use LocalSet here. Instead /// // we use spawn_blocking in order to use LocalSet::block_on /// tokio::task::spawn_blocking(move || { /// // LocalSet allows us to work with !Send futures within tokio. Without it, any calls to /// // pyo3_async_runtimes::tokio::local_future_into_py will panic. /// tokio::task::LocalSet::new().block_on( /// pyo3_async_runtimes::tokio::get_runtime(), /// pyo3_async_runtimes::tokio::scope_local(locals, async { /// Python::with_gil(|py| { /// let py_future = sleep_for(py, 1)?; /// pyo3_async_runtimes::tokio::into_future(py_future) /// })? /// .await?; /// /// Ok(()) /// }) /// ) /// }).await.unwrap() /// } /// # #[cfg(not(all(feature = "tokio-runtime", feature = "attributes")))] /// # fn main() {} /// ``` #[deprecated( since = "0.18.0", note = "Questionable whether these conversions have real-world utility (see https://github.com/awestlake87/pyo3-asyncio/issues/59#issuecomment-1008038497 and let me know if you disagree!)" )] #[allow(deprecated)] pub fn local_future_into_py(py: Python, fut: F) -> PyResult> where F: Future> + 'static, T: IntoPy, { generic::local_future_into_py::(py, fut) } /// Convert a Python `awaitable` into a Rust Future /// /// This function converts the `awaitable` into a Python Task using `run_coroutine_threadsafe`. A /// completion handler sends the result of this Task through a /// `futures::channel::oneshot::Sender>` and the future returned by this function /// simply awaits the result through the `futures::channel::oneshot::Receiver>`. /// /// # Arguments /// * `awaitable` - The Python `awaitable` to be converted /// /// # Examples /// /// ``` /// use std::time::Duration; /// /// use pyo3::prelude::*; /// /// const PYTHON_CODE: &'static str = r#" /// import asyncio /// /// async def py_sleep(duration): /// await asyncio.sleep(duration) /// "#; /// /// async fn py_sleep(seconds: f32) -> PyResult<()> { /// let test_mod = Python::with_gil(|py| -> PyResult { /// Ok( /// PyModule::from_code_bound( /// py, /// PYTHON_CODE, /// "test_into_future/test_mod.py", /// "test_mod" /// )? /// .into() /// ) /// })?; /// /// Python::with_gil(|py| { /// pyo3_async_runtimes::tokio::into_future( /// test_mod /// .call_method1(py, "py_sleep", (seconds.into_py(py),))? /// .into_bound(py), /// ) /// })? /// .await?; /// Ok(()) /// } /// ``` pub fn into_future( awaitable: Bound, ) -> PyResult> + Send> { generic::into_future::(awaitable) } /// unstable-streams Convert an async generator into a stream /// /// **This API is marked as unstable** and is only available when the /// `unstable-streams` crate feature is enabled. This comes with no /// stability guarantees, and could be changed or removed at any time. /// /// # Arguments /// * `locals` - The current task locals /// * `gen` - The Python async generator to be converted /// /// # Examples /// ``` /// use pyo3::prelude::*; /// use futures::{StreamExt, TryStreamExt}; /// /// const TEST_MOD: &str = r#" /// import asyncio /// /// async def gen(): /// for i in range(10): /// await asyncio.sleep(0.1) /// yield i /// "#; /// /// # #[cfg(all(feature = "unstable-streams", feature = "attributes"))] /// # #[pyo3_async_runtimes::tokio::main] /// # async fn main() -> PyResult<()> { /// let stream = Python::with_gil(|py| { /// let test_mod = PyModule::from_code_bound( /// py, /// TEST_MOD, /// "test_rust_coroutine/test_mod.py", /// "test_mod", /// )?; /// /// pyo3_async_runtimes::tokio::into_stream_with_locals_v1( /// pyo3_async_runtimes::tokio::get_current_locals(py)?, /// test_mod.call_method0("gen")? /// ) /// })?; /// /// let vals = stream /// .map(|item| Python::with_gil(|py| -> PyResult { Ok(item?.bind(py).extract()?) })) /// .try_collect::>() /// .await?; /// /// assert_eq!((0..10).collect::>(), vals); /// /// Ok(()) /// # } /// # #[cfg(not(all(feature = "unstable-streams", feature = "attributes")))] /// # fn main() {} /// ``` #[cfg(feature = "unstable-streams")] pub fn into_stream_with_locals_v1( locals: TaskLocals, gen: Bound<'_, PyAny>, ) -> PyResult> + 'static> { generic::into_stream_with_locals_v1::(locals, gen) } /// unstable-streams Convert an async generator into a stream /// /// **This API is marked as unstable** and is only available when the /// `unstable-streams` crate feature is enabled. This comes with no /// stability guarantees, and could be changed or removed at any time. /// /// # Arguments /// * `gen` - The Python async generator to be converted /// /// # Examples /// ``` /// use pyo3::prelude::*; /// use futures::{StreamExt, TryStreamExt}; /// /// const TEST_MOD: &str = r#" /// import asyncio /// /// async def gen(): /// for i in range(10): /// await asyncio.sleep(0.1) /// yield i /// "#; /// /// # #[cfg(all(feature = "unstable-streams", feature = "attributes"))] /// # #[pyo3_async_runtimes::tokio::main] /// # async fn main() -> PyResult<()> { /// let stream = Python::with_gil(|py| { /// let test_mod = PyModule::from_code_bound( /// py, /// TEST_MOD, /// "test_rust_coroutine/test_mod.py", /// "test_mod", /// )?; /// /// pyo3_async_runtimes::tokio::into_stream_v1(test_mod.call_method0("gen")?) /// })?; /// /// let vals = stream /// .map(|item| Python::with_gil(|py| -> PyResult { Ok(item?.bind(py).extract()?) })) /// .try_collect::>() /// .await?; /// /// assert_eq!((0..10).collect::>(), vals); /// /// Ok(()) /// # } /// # #[cfg(not(all(feature = "unstable-streams", feature = "attributes")))] /// # fn main() {} /// ``` #[cfg(feature = "unstable-streams")] pub fn into_stream_v1( gen: Bound<'_, PyAny>, ) -> PyResult> + 'static> { generic::into_stream_v1::(gen) } /// unstable-streams Convert an async generator into a stream /// /// **This API is marked as unstable** and is only available when the /// `unstable-streams` crate feature is enabled. This comes with no /// stability guarantees, and could be changed or removed at any time. /// /// # Arguments /// * `locals` - The current task locals /// * `gen` - The Python async generator to be converted /// /// # Examples /// ``` /// use pyo3::prelude::*; /// use futures::{StreamExt, TryStreamExt}; /// /// const TEST_MOD: &str = r#" /// import asyncio /// /// async def gen(): /// for i in range(10): /// await asyncio.sleep(0.1) /// yield i /// "#; /// /// # #[cfg(all(feature = "unstable-streams", feature = "attributes"))] /// # #[pyo3_async_runtimes::tokio::main] /// # async fn main() -> PyResult<()> { /// let stream = Python::with_gil(|py| { /// let test_mod = PyModule::from_code_bound( /// py, /// TEST_MOD, /// "test_rust_coroutine/test_mod.py", /// "test_mod", /// )?; /// /// pyo3_async_runtimes::tokio::into_stream_with_locals_v2( /// pyo3_async_runtimes::tokio::get_current_locals(py)?, /// test_mod.call_method0("gen")? /// ) /// })?; /// /// let vals = stream /// .map(|item| Python::with_gil(|py| -> PyResult { Ok(item.bind(py).extract()?) })) /// .try_collect::>() /// .await?; /// /// assert_eq!((0..10).collect::>(), vals); /// /// Ok(()) /// # } /// # #[cfg(not(all(feature = "unstable-streams", feature = "attributes")))] /// # fn main() {} /// ``` #[cfg(feature = "unstable-streams")] pub fn into_stream_with_locals_v2( locals: TaskLocals, gen: Bound<'_, PyAny>, ) -> PyResult + 'static> { generic::into_stream_with_locals_v2::(locals, gen) } /// unstable-streams Convert an async generator into a stream /// /// **This API is marked as unstable** and is only available when the /// `unstable-streams` crate feature is enabled. This comes with no /// stability guarantees, and could be changed or removed at any time. /// /// # Arguments /// * `gen` - The Python async generator to be converted /// /// # Examples /// ``` /// use pyo3::prelude::*; /// use futures::{StreamExt, TryStreamExt}; /// /// const TEST_MOD: &str = r#" /// import asyncio /// /// async def gen(): /// for i in range(10): /// await asyncio.sleep(0.1) /// yield i /// "#; /// /// # #[cfg(all(feature = "unstable-streams", feature = "attributes"))] /// # #[pyo3_async_runtimes::tokio::main] /// # async fn main() -> PyResult<()> { /// let stream = Python::with_gil(|py| { /// let test_mod = PyModule::from_code_bound( /// py, /// TEST_MOD, /// "test_rust_coroutine/test_mod.py", /// "test_mod", /// )?; /// /// pyo3_async_runtimes::tokio::into_stream_v2(test_mod.call_method0("gen")?) /// })?; /// /// let vals = stream /// .map(|item| Python::with_gil(|py| -> PyResult { Ok(item.bind(py).extract()?) })) /// .try_collect::>() /// .await?; /// /// assert_eq!((0..10).collect::>(), vals); /// /// Ok(()) /// # } /// # #[cfg(not(all(feature = "unstable-streams", feature = "attributes")))] /// # fn main() {} /// ``` #[cfg(feature = "unstable-streams")] pub fn into_stream_v2( gen: Bound<'_, PyAny>, ) -> PyResult + 'static> { generic::into_stream_v2::(gen) }