├── .cargo └── config ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── docs ├── Makefile ├── conf.py ├── index.rst ├── make.bat ├── requirements.txt ├── zenoh-flow-api.rst └── zenoh-flow-nodes.rst ├── examples ├── flow.yaml ├── sink.py └── zenoh-flow-configuration.yaml ├── rust-toolchain.toml ├── zenoh-flow-python-operator-wrapper ├── Cargo.toml └── src │ └── lib.rs ├── zenoh-flow-python-sink-wrapper ├── Cargo.toml └── src │ └── lib.rs ├── zenoh-flow-python-source-wrapper ├── Cargo.toml └── src │ └── lib.rs └── zenoh-flow-python ├── Cargo.toml ├── pyproject.toml ├── requirements-dev.txt ├── src └── lib.rs └── zenoh_flow_python ├── __init__.py ├── nodes.py ├── py.typed └── zenoh_flow_python.pyi /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.x86_64-unknown-linux-musl] 2 | rustflags = "-Ctarget-feature=-crt-static" 3 | 4 | [target.aarch64-unknown-linux-musl] 5 | rustflags = "-Ctarget-feature=-crt-static" -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: ["master"] 9 | pull_request: 10 | branches: ["master"] 11 | schedule: 12 | - cron: "0 0 * * 1-5" 13 | workflow_dispatch: 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | python-version: ["3.7", "3.8", "3.9", "3.10"] 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | with: 26 | submodules: true 27 | 28 | - name: Set up Python ${{ matrix.python-version }} 29 | uses: actions/setup-python@v2 30 | with: 31 | python-version: ${{ matrix.python-version }} 32 | 33 | - name: Install Rust toolchain 34 | uses: actions-rs/toolchain@v1 35 | with: 36 | components: rustfmt, clippy 37 | 38 | - name: Clippy check 39 | uses: actions-rs/cargo@v1 40 | with: 41 | command: clippy 42 | args: --all-targets -- -D warnings 43 | 44 | - name: Run rustfmt 45 | uses: actions-rs/cargo@v1 46 | with: 47 | command: fmt 48 | args: -- --check 49 | 50 | - name: Build Wrappers 51 | uses: actions-rs/cargo@v1 52 | with: 53 | command: build 54 | args: --release -p zenoh-flow-python-source-wrapper -p zenoh-flow-python-sink-wrapper -p zenoh-flow-python-operator-wrapper 55 | 56 | - name: Build zenoh-flow-python 57 | uses: PyO3/maturin-action@v1 58 | with: 59 | args: --release -m zenoh-flow-python/Cargo.toml 60 | 61 | - name: Install zenoh-flow-python 62 | run: pip3 install ./target/wheels/*.whl 63 | 64 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Release 5 | 6 | on: 7 | release: 8 | types: [published] 9 | schedule: 10 | - cron: "0 1 * * 1-5" 11 | workflow_dispatch: 12 | 13 | jobs: 14 | checks: 15 | name: Code checks 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Install Rust toolchain 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | components: rustfmt, clippy 23 | - name: Environment setup 24 | id: env 25 | shell: bash 26 | run: | 27 | # log some info 28 | gcc --version || true 29 | rustup -V 30 | rustup toolchain list 31 | rustup default 32 | cargo -V 33 | rustc -V 34 | echo "GITHUB_REF=${GITHUB_REF}" 35 | echo "GITHUB_SHA=${GITHUB_SHA:0:8}" 36 | GIT_BRANCH=`[[ $GITHUB_REF =~ ^refs/heads/.* ]] && echo ${GITHUB_REF/refs\/heads\//} || true` 37 | echo "GIT_BRANCH=${GIT_BRANCH}" >> $GITHUB_OUTPUT 38 | GIT_TAG=`[[ $GITHUB_REF =~ ^refs/tags/.* ]] && echo ${GITHUB_REF/refs\/tags\//} || true` 39 | echo "GIT_TAG=${GIT_TAG}" >> $GITHUB_OUTPUT 40 | ZENOH_FLOW_VERSION=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1) 41 | echo "ZENOH_FLOW_VERSION=${ZENOH_FLOW_VERSION}" >> $GITHUB_OUTPUT 42 | if [ -n "${GIT_TAG}" ]; then 43 | IS_RELEASE="true" 44 | echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT 45 | PKG_VERSION=${ZENOH_FLOW_VERSION} 46 | elif [ -n "${GIT_BRANCH}" ]; then 47 | PKG_VERSION=${GIT_BRANCH}-${GITHUB_SHA:0:8} 48 | else 49 | PKG_VERSION=${ZENOH_FLOW_VERSION}-${GITHUB_SHA:0:8} 50 | fi 51 | echo "PKG_VERSION=${PKG_VERSION}" >> $GITHUB_OUTPUT 52 | 53 | echo "GIT_TAG=${GIT_TAG}" 54 | echo "GIT_BRANCH=${GIT_BRANCH}" 55 | echo "IS_RELEASE=${IS_RELEASE}" 56 | echo "ZENOH_FLOW_VERSION=${ZENOH_FLOW_VERSION}" 57 | echo "PKG_VERSION=${PKG_VERSION}" 58 | outputs: 59 | GIT_BRANCH: ${{ steps.env.outputs.GIT_BRANCH }} 60 | GIT_TAG: ${{ steps.env.outputs.GIT_TAG }} 61 | IS_RELEASE: ${{ steps.env.outputs.IS_RELEASE }} 62 | ZENOH_FLOW_VERSION: ${{ steps.env.outputs.ZENOH_FLOW_VERSION }} 63 | PKG_VERSION: ${{ steps.env.outputs.PKG_VERSION }} 64 | 65 | 66 | 67 | linux-python: 68 | runs-on: ubuntu-latest 69 | strategy: 70 | fail-fast: true 71 | matrix: 72 | target: [x86_64, i686] 73 | steps: 74 | - uses: actions/checkout@v2 75 | - name: Set up Python 3.7 76 | uses: actions/setup-python@v2 77 | with: 78 | python-version: 3.7 79 | architecture: x64 80 | - name: Install Rust toolchain 81 | uses: actions-rs/toolchain@v1 82 | with: 83 | components: rustfmt, clippy 84 | - name: Build zenoh-flow-python 85 | uses: messense/maturin-action@v1 86 | with: 87 | target: ${{ matrix.target }} 88 | manylinux: auto 89 | args: --release -m zenoh-flow-python/Cargo.toml --out dist 90 | - name: Upload wheels 91 | uses: actions/upload-artifact@v2 92 | with: 93 | name: wheels 94 | path: dist 95 | 96 | macos-python: 97 | runs-on: macos-latest 98 | steps: 99 | - uses: actions/checkout@v2 100 | - name: Set up Python 3.7 101 | uses: actions/setup-python@v2 102 | with: 103 | python-version: 3.7 104 | architecture: x64 105 | - name: Install Rust toolchain 106 | uses: actions-rs/toolchain@v1 107 | with: 108 | components: rustfmt, clippy 109 | - name: Build zenoh-flow-python - x86_64 110 | uses: messense/maturin-action@v1 111 | with: 112 | target: ${{ matrix.target }} 113 | manylinux: auto 114 | args: --release -m zenoh-flow-python/Cargo.toml --out dist 115 | - name: Build zenoh-flow-python - universal2 116 | uses: messense/maturin-action@v1 117 | with: 118 | target: ${{ matrix.target }} 119 | manylinux: auto 120 | args: --release -m zenoh-flow-python/Cargo.toml --out dist --universal2 --no-sdist 121 | - name: Upload wheels 122 | uses: actions/upload-artifact@v2 123 | with: 124 | name: wheels 125 | path: dist 126 | 127 | linux-cross-python: 128 | runs-on: ubuntu-latest 129 | strategy: 130 | matrix: 131 | target: [aarch64, armv7] 132 | steps: 133 | - uses: actions/checkout@v2 134 | - uses: actions/setup-python@v2 135 | with: 136 | python-version: "3.7" 137 | - name: Build zenoh-flow-python 138 | uses: messense/maturin-action@v1 139 | with: 140 | target: ${{ matrix.target }} 141 | manylinux: auto 142 | args: --release -m zenoh-flow-python/Cargo.toml --out dist --no-sdist 143 | - name: Upload wheels 144 | uses: actions/upload-artifact@v2 145 | with: 146 | name: wheels 147 | path: dist 148 | 149 | # disabled because of rust-toolchain installation failing 150 | # linux-armv6-python: 151 | # runs-on: macos-latest 152 | # steps: 153 | # - uses: actions/checkout@v2 154 | # - uses: actions/setup-python@v2 155 | # with: 156 | # python-version: "3.7" 157 | # - name: Install Rust toolchain 158 | # uses: actions-rs/toolchain@v1 159 | # with: 160 | # components: rustfmt, clippy 161 | # target: arm-unknown-linux-gnueabihf 162 | # default: true 163 | # - name: install cross toolchain 164 | # run: | 165 | # brew tap messense/macos-cross-toolchains 166 | # brew install arm-unknown-linux-gnueabihf 167 | 168 | # export CC_arm_unknown_linux_gnueabihf=arm-unknown-linux-gnueabihf-gcc 169 | # export CXX_arm_unknown_linux_gnueabihf=arm-unknown-linux-gnueabihf-g++ 170 | # export AR_arm_unknown_linux_gnueabihf=arm-unknown-linux-gnueabihf-ar 171 | # export CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-unknown-linux-gnueabihf-gcc 172 | 173 | # pip3 install maturin 174 | # maturin build --release --target arm-unknown-linux-gnueabihf --out dist -m zenoh-flow-python/Cargo.toml 175 | # - name: Upload wheels 176 | # uses: actions/upload-artifact@v2 177 | # with: 178 | # name: wheels 179 | # path: dists 180 | 181 | deploy-wheels: 182 | needs: [macos-python, linux-python, linux-cross-python] 183 | name: deploy wheels to pypi 184 | runs-on: ubuntu-latest 185 | steps: 186 | - uses: actions/download-artifact@v2 187 | with: 188 | name: wheels 189 | path: dist 190 | - name: Check dist 191 | run: ls -al ./dist/* 192 | - name: publish on pypi.org 193 | if: github.event_name == 'release' && github.event.action == 'published' 194 | uses: pypa/gh-action-pypi-publish@master 195 | with: 196 | user: __token__ 197 | password: ${{ secrets.PYPI_API_TOKEN }} 198 | 199 | 200 | builds: 201 | name: Build for ${{ matrix.job.target }} on ${{ matrix.job.os }} 202 | needs: [checks] 203 | runs-on: ${{ matrix.job.os }} 204 | strategy: 205 | fail-fast: false 206 | matrix: 207 | job: 208 | - { target: x86_64-unknown-linux-gnu, arch: amd64, os: ubuntu-22.04 } 209 | - { target: x86_64-unknown-linux-gnu, arch: amd64, os: ubuntu-20.04 } 210 | - { target: x86_64-unknown-linux-gnu, arch: amd64, os: ubuntu-18.04 } 211 | - { target: x86_64-apple-darwin, arch: darwin, os: macos-latest } 212 | steps: 213 | - uses: actions/checkout@v2 214 | - name: Install prerequisites 215 | shell: bash 216 | run: | 217 | case ${{ matrix.job.target }} in 218 | *-linux-gnu*) 219 | cargo install cargo-deb 220 | sudo apt-get install libpython3-dev equivs 221 | ;; 222 | esac 223 | - name: Set up Python 3.7 224 | if: ${{ matrix.job.target }} == 'x86_64-apple-darwin' 225 | uses: actions/setup-python@v2 226 | with: 227 | python-version: "3.7" 228 | - name: Install Rust toolchain 229 | uses: actions-rs/toolchain@v1 230 | with: 231 | components: rustfmt, clippy 232 | target: ${{ matrix.job.target }} 233 | 234 | - name: Build Wrappers 235 | uses: actions-rs/cargo@v1 236 | with: 237 | use-cross: ${{ matrix.job.use-cross }} 238 | command: build 239 | args: --release -p zenoh-flow-python-source-wrapper -p zenoh-flow-python-sink-wrapper -p zenoh-flow-python-operator-wrapper --target=${{ matrix.job.target }} --all-targets 240 | 241 | - name: Debian package - zenoh-flow-python-source-wrapper 242 | if: runner.os == 'Linux' 243 | uses: actions-rs/cargo@v1 244 | with: 245 | command: deb 246 | args: --no-build -p zenoh-flow-python-source-wrapper --target=${{ matrix.job.target }} 247 | 248 | - name: Debian package - zenoh-flow-python-sink-wrapper 249 | if: runner.os == 'Linux' 250 | uses: actions-rs/cargo@v1 251 | with: 252 | command: deb 253 | args: --no-build -p zenoh-flow-python-sink-wrapper --target=${{ matrix.job.target }} 254 | 255 | - name: Debian package - zenoh-flow-python-operator-wrapper 256 | if: runner.os == 'Linux' 257 | uses: actions-rs/cargo@v1 258 | with: 259 | command: deb 260 | args: --no-build -p zenoh-flow-python-operator-wrapper --target=${{ matrix.job.target }} 261 | 262 | - name: Build metapackage 263 | if: runner.os == 'Linux' 264 | shell: bash 265 | run: | 266 | equivs-build zenoh-flow-python-extension 267 | equivs-build zenoh-flow-python-extension-plugin 268 | 269 | - name: Packaging 270 | id: package 271 | shell: bash 272 | run: | 273 | TARGET=${{ matrix.job.target }} 274 | MAIN_PKG_NAME="${GITHUB_WORKSPACE}/zenoh-flow-python-${{ needs.checks.outputs.PKG_VERSION }}-${TARGET}.zip" 275 | DEBS_PKG_NAME="${GITHUB_WORKSPACE}/zenoh-flow-python-${{ needs.checks.outputs.PKG_VERSION }}-${TARGET}-deb-pkgs.zip" 276 | case ${TARGET} in 277 | *linux*) 278 | cd "target/${TARGET}/release/" 279 | echo "Packaging ${MAIN_PKG_NAME}:" 280 | zip ${MAIN_PKG_NAME} libzenoh_flow_python_*_wrapper.so 281 | cd - 282 | echo MAIN_PKG_NAME="${MAIN_PKG_NAME}" >> $GITHUB_OUTPUT 283 | 284 | # check if debian packages has been created and packages them in a single tgz 285 | if [[ -d target/${TARGET}/debian ]]; then 286 | cd target/${TARGET}/debian 287 | echo "Packaging ${DEBS_PKG_NAME}:" 288 | zip ${DEBS_PKG_NAME} *.deb 289 | cd - 290 | # checks if the meta package exists and adds it to the zip 291 | if [[ -d zenoh-flow-python*.deb ]]; then 292 | zip -ru ${DEBS_PKG_NAME} zenoh-flow-python*.deb 293 | fi 294 | echo DEBS_PKG_NAME="${DEBS_PKG_NAME}" >> $GITHUB_OUTPUT 295 | fi 296 | ;; 297 | *apple*) 298 | cd "target/${TARGET}/release/" 299 | echo "Packaging ${MAIN_PKG_NAME}:" 300 | zip ${MAIN_PKG_NAME} libzenoh_flow_python_*_wrapper.dylib 301 | cd - 302 | echo MAIN_PKG_NAME="${MAIN_PKG_NAME}" >> $GITHUB_OUTPUT 303 | ;; 304 | esac 305 | 306 | - name: "Upload packages" 307 | uses: actions/upload-artifact@master 308 | with: 309 | name: ${{ matrix.job.target }} 310 | path: | 311 | ${{ steps.package.outputs.MAIN_PKG_NAME }} 312 | ${{ steps.package.outputs.DEBS_PKG_NAME }} 313 | 314 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | # Generated by Cargo 163 | # will have compiled files and executables 164 | debug/ 165 | target/ 166 | 167 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 168 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 169 | Cargo.lock 170 | 171 | # These are backup files generated by rustfmt 172 | **/*.rs.bk 173 | 174 | # MSVC Windows builds of rustc generate these, which store debugging information 175 | *.pdb 176 | 177 | 178 | /venv 179 | Cargo.lock 180 | build/ 181 | dist/ 182 | *.so 183 | _build/ 184 | *.pyc 185 | *.patch -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Eclipse zenoh 2 | 3 | Thanks for your interest in this project. 4 | 5 | ## Project description 6 | 7 | Eclipse zenoh provides is a stack designed to 8 | 1. minimize network overhead, 9 | 2. support extremely constrained devices, 10 | 3. supports devices with low duty-cycle by allowing the negotiation of data exchange modes and schedules, 11 | 4. provide a rich set of abstraction for distributing, querying and storing data along the entire system, and 12 | 5. provide extremely low latency and high throughput. 13 | 14 | * https://projects.eclipse.org/projects/iot.zenoh 15 | 16 | ## Developer resources 17 | 18 | Information regarding source code management, builds, coding standards, and 19 | more. 20 | 21 | * https://projects.eclipse.org/projects/iot.zenoh/developer 22 | 23 | The project maintains the following source code repositories 24 | 25 | * https://github.com/eclipse-zenoh 26 | 27 | ## Eclipse Contributor Agreement 28 | 29 | Before your contribution can be accepted by the project team contributors must 30 | electronically sign the Eclipse Contributor Agreement (ECA). 31 | 32 | * http://www.eclipse.org/legal/ECA.php 33 | 34 | Commits that are provided by non-committers must have a Signed-off-by field in 35 | the footer indicating that the author is aware of the terms by which the 36 | contribution has been provided to the project. The non-committer must 37 | additionally have an Eclipse Foundation account and must have a signed Eclipse 38 | Contributor Agreement (ECA) on file. 39 | 40 | For more information, please see the Eclipse Committer Handbook: 41 | https://www.eclipse.org/projects/handbook/#resources-commit 42 | 43 | ## Contact 44 | 45 | Contact the project developers via the project's "dev" list. 46 | 47 | * https://accounts.eclipse.org/mailing-list/zenoh-dev 48 | 49 | Or via the Gitter channel. 50 | 51 | * https://gitter.im/atolab/zenoh 52 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright © 2022 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # ZettaScale Zenoh Team, 13 | # 14 | 15 | [workspace] 16 | resolver = "2" 17 | members = [ 18 | "zenoh-flow-python-operator-wrapper", 19 | "zenoh-flow-python-sink-wrapper", 20 | "zenoh-flow-python-source-wrapper", 21 | "zenoh-flow-python", 22 | ] 23 | 24 | [workspace.package] 25 | authors = ["ZettaScale Zenoh Team "] 26 | categories = ["network-programming"] 27 | description = "Zenoh-Flow: a Zenoh-based data flow programming framework for computations that span from the cloud to the device." 28 | edition = "2021" 29 | homepage = "https://github.com/eclipse-zenoh-flow/zenoh-flow" 30 | license = " EPL-2.0 OR Apache-2.0" 31 | readme = "README.md" 32 | repository = "https://github.com/eclipse-zenoh-flow/zenoh-flow-python" 33 | version = "0.6.0-dev" 34 | 35 | [workspace.dependencies] 36 | anyhow = { version = "1" } 37 | async-std = { version = "1.12.0", features = ["attributes"] } 38 | async-trait = "0.1" 39 | pyo3 = { version = "0.20", features = ["auto-initialize", "abi3-py38"] } 40 | pyo3-asyncio = { version = "0.20", features = ["attributes", "async-std-runtime"] } 41 | pyo3-pylogger = "0.2" 42 | serde_json = { version = "1.0" } 43 | tracing = "0.1" 44 | tracing-subscriber = "0.3" 45 | zenoh-flow-nodes = { git = "https://github.com/eclipse-zenoh-flow/zenoh-flow.git", branch = "main" } 46 | zenoh-flow-python = { path = "./zenoh-flow-python" } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | apache-2.0 2 | epl-2.0 3 | 4 | 5 | Apache License 6 | Version 2.0, January 2004 7 | http://www.apache.org/licenses/ 8 | 9 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 10 | 11 | 1. Definitions. 12 | 13 | "License" shall mean the terms and conditions for use, reproduction, 14 | and distribution as defined by Sections 1 through 9 of this document. 15 | 16 | "Licensor" shall mean the copyright owner or entity authorized by 17 | the copyright owner that is granting the License. 18 | 19 | "Legal Entity" shall mean the union of the acting entity and all 20 | other entities that control, are controlled by, or are under common 21 | control with that entity. For the purposes of this definition, 22 | "control" means (i) the power, direct or indirect, to cause the 23 | direction or management of such entity, whether by contract or 24 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 25 | outstanding shares, or (iii) beneficial ownership of such entity. 26 | 27 | "You" (or "Your") shall mean an individual or Legal Entity 28 | exercising permissions granted by this License. 29 | 30 | "Source" form shall mean the preferred form for making modifications, 31 | including but not limited to software source code, documentation 32 | source, and configuration files. 33 | 34 | "Object" form shall mean any form resulting from mechanical 35 | transformation or translation of a Source form, including but 36 | not limited to compiled object code, generated documentation, 37 | and conversions to other media types. 38 | 39 | "Work" shall mean the work of authorship, whether in Source or 40 | Object form, made available under the License, as indicated by a 41 | copyright notice that is included in or attached to the work 42 | (an example is provided in the Appendix below). 43 | 44 | "Derivative Works" shall mean any work, whether in Source or Object 45 | form, that is based on (or derived from) the Work and for which the 46 | editorial revisions, annotations, elaborations, or other modifications 47 | represent, as a whole, an original work of authorship. For the purposes 48 | of this License, Derivative Works shall not include works that remain 49 | separable from, or merely link (or bind by name) to the interfaces of, 50 | the Work and Derivative Works thereof. 51 | 52 | "Contribution" shall mean any work of authorship, including 53 | the original version of the Work and any modifications or additions 54 | to that Work or Derivative Works thereof, that is intentionally 55 | submitted to Licensor for inclusion in the Work by the copyright owner 56 | or by an individual or Legal Entity authorized to submit on behalf of 57 | the copyright owner. For the purposes of this definition, "submitted" 58 | means any form of electronic, verbal, or written communication sent 59 | to the Licensor or its representatives, including but not limited to 60 | communication on electronic mailing lists, source code control systems, 61 | and issue tracking systems that are managed by, or on behalf of, the 62 | Licensor for the purpose of discussing and improving the Work, but 63 | excluding communication that is conspicuously marked or otherwise 64 | designated in writing by the copyright owner as "Not a Contribution." 65 | 66 | "Contributor" shall mean Licensor and any individual or Legal Entity 67 | on behalf of whom a Contribution has been received by Licensor and 68 | subsequently incorporated within the Work. 69 | 70 | 2. Grant of Copyright License. Subject to the terms and conditions of 71 | this License, each Contributor hereby grants to You a perpetual, 72 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 73 | copyright license to reproduce, prepare Derivative Works of, 74 | publicly display, publicly perform, sublicense, and distribute the 75 | Work and such Derivative Works in Source or Object form. 76 | 77 | 3. Grant of Patent License. Subject to the terms and conditions of 78 | this License, each Contributor hereby grants to You a perpetual, 79 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 80 | (except as stated in this section) patent license to make, have made, 81 | use, offer to sell, sell, import, and otherwise transfer the Work, 82 | where such license applies only to those patent claims licensable 83 | by such Contributor that are necessarily infringed by their 84 | Contribution(s) alone or by combination of their Contribution(s) 85 | with the Work to which such Contribution(s) was submitted. If You 86 | institute patent litigation against any entity (including a 87 | cross-claim or counterclaim in a lawsuit) alleging that the Work 88 | or a Contribution incorporated within the Work constitutes direct 89 | or contributory patent infringement, then any patent licenses 90 | granted to You under this License for that Work shall terminate 91 | as of the date such litigation is filed. 92 | 93 | 4. Redistribution. You may reproduce and distribute copies of the 94 | Work or Derivative Works thereof in any medium, with or without 95 | modifications, and in Source or Object form, provided that You 96 | meet the following conditions: 97 | 98 | (a) You must give any other recipients of the Work or 99 | Derivative Works a copy of this License; and 100 | 101 | (b) You must cause any modified files to carry prominent notices 102 | stating that You changed the files; and 103 | 104 | (c) You must retain, in the Source form of any Derivative Works 105 | that You distribute, all copyright, patent, trademark, and 106 | attribution notices from the Source form of the Work, 107 | excluding those notices that do not pertain to any part of 108 | the Derivative Works; and 109 | 110 | (d) If the Work includes a "NOTICE" text file as part of its 111 | distribution, then any Derivative Works that You distribute must 112 | include a readable copy of the attribution notices contained 113 | within such NOTICE file, excluding those notices that do not 114 | pertain to any part of the Derivative Works, in at least one 115 | of the following places: within a NOTICE text file distributed 116 | as part of the Derivative Works; within the Source form or 117 | documentation, if provided along with the Derivative Works; or, 118 | within a display generated by the Derivative Works, if and 119 | wherever such third-party notices normally appear. The contents 120 | of the NOTICE file are for informational purposes only and 121 | do not modify the License. You may add Your own attribution 122 | notices within Derivative Works that You distribute, alongside 123 | or as an addendum to the NOTICE text from the Work, provided 124 | that such additional attribution notices cannot be construed 125 | as modifying the License. 126 | 127 | You may add Your own copyright statement to Your modifications and 128 | may provide additional or different license terms and conditions 129 | for use, reproduction, or distribution of Your modifications, or 130 | for any such Derivative Works as a whole, provided Your use, 131 | reproduction, and distribution of the Work otherwise complies with 132 | the conditions stated in this License. 133 | 134 | 5. Submission of Contributions. Unless You explicitly state otherwise, 135 | any Contribution intentionally submitted for inclusion in the Work 136 | by You to the Licensor shall be under the terms and conditions of 137 | this License, without any additional terms or conditions. 138 | Notwithstanding the above, nothing herein shall supersede or modify 139 | the terms of any separate license agreement you may have executed 140 | with Licensor regarding such Contributions. 141 | 142 | 6. Trademarks. This License does not grant permission to use the trade 143 | names, trademarks, service marks, or product names of the Licensor, 144 | except as required for reasonable and customary use in describing the 145 | origin of the Work and reproducing the content of the NOTICE file. 146 | 147 | 7. Disclaimer of Warranty. Unless required by applicable law or 148 | agreed to in writing, Licensor provides the Work (and each 149 | Contributor provides its Contributions) on an "AS IS" BASIS, 150 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 151 | implied, including, without limitation, any warranties or conditions 152 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 153 | PARTICULAR PURPOSE. You are solely responsible for determining the 154 | appropriateness of using or redistributing the Work and assume any 155 | risks associated with Your exercise of permissions under this License. 156 | 157 | 8. Limitation of Liability. In no event and under no legal theory, 158 | whether in tort (including negligence), contract, or otherwise, 159 | unless required by applicable law (such as deliberate and grossly 160 | negligent acts) or agreed to in writing, shall any Contributor be 161 | liable to You for damages, including any direct, indirect, special, 162 | incidental, or consequential damages of any character arising as a 163 | result of this License or out of the use or inability to use the 164 | Work (including but not limited to damages for loss of goodwill, 165 | work stoppage, computer failure or malfunction, or any and all 166 | other commercial damages or losses), even if such Contributor 167 | has been advised of the possibility of such damages. 168 | 169 | 9. Accepting Warranty or Additional Liability. While redistributing 170 | the Work or Derivative Works thereof, You may choose to offer, 171 | and charge a fee for, acceptance of support, warranty, indemnity, 172 | or other liability obligations and/or rights consistent with this 173 | License. However, in accepting such obligations, You may act only 174 | on Your own behalf and on Your sole responsibility, not on behalf 175 | of any other Contributor, and only if You agree to indemnify, 176 | defend, and hold each Contributor harmless for any liability 177 | incurred by, or claims asserted against, such Contributor by reason 178 | of your accepting any such warranty or additional liability. 179 | 180 | OR 181 | 182 | Eclipse Public License - v 2.0 183 | 184 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 185 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION 186 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 187 | 188 | 1. DEFINITIONS 189 | 190 | "Contribution" means: 191 | 192 | a) in the case of the initial Contributor, the initial content 193 | Distributed under this Agreement, and 194 | 195 | b) in the case of each subsequent Contributor: 196 | i) changes to the Program, and 197 | ii) additions to the Program; 198 | where such changes and/or additions to the Program originate from 199 | and are Distributed by that particular Contributor. A Contribution 200 | "originates" from a Contributor if it was added to the Program by 201 | such Contributor itself or anyone acting on such Contributor's behalf. 202 | Contributions do not include changes or additions to the Program that 203 | are not Modified Works. 204 | 205 | "Contributor" means any person or entity that Distributes the Program. 206 | 207 | "Licensed Patents" mean patent claims licensable by a Contributor which 208 | are necessarily infringed by the use or sale of its Contribution alone 209 | or when combined with the Program. 210 | 211 | "Program" means the Contributions Distributed in accordance with this 212 | Agreement. 213 | 214 | "Recipient" means anyone who receives the Program under this Agreement 215 | or any Secondary License (as applicable), including Contributors. 216 | 217 | "Derivative Works" shall mean any work, whether in Source Code or other 218 | form, that is based on (or derived from) the Program and for which the 219 | editorial revisions, annotations, elaborations, or other modifications 220 | represent, as a whole, an original work of authorship. 221 | 222 | "Modified Works" shall mean any work in Source Code or other form that 223 | results from an addition to, deletion from, or modification of the 224 | contents of the Program, including, for purposes of clarity any new file 225 | in Source Code form that contains any contents of the Program. Modified 226 | Works shall not include works that contain only declarations, 227 | interfaces, types, classes, structures, or files of the Program solely 228 | in each case in order to link to, bind by name, or subclass the Program 229 | or Modified Works thereof. 230 | 231 | "Distribute" means the acts of a) distributing or b) making available 232 | in any manner that enables the transfer of a copy. 233 | 234 | "Source Code" means the form of a Program preferred for making 235 | modifications, including but not limited to software source code, 236 | documentation source, and configuration files. 237 | 238 | "Secondary License" means either the GNU General Public License, 239 | Version 2.0, or any later versions of that license, including any 240 | exceptions or additional permissions as identified by the initial 241 | Contributor. 242 | 243 | 2. GRANT OF RIGHTS 244 | 245 | a) Subject to the terms of this Agreement, each Contributor hereby 246 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 247 | license to reproduce, prepare Derivative Works of, publicly display, 248 | publicly perform, Distribute and sublicense the Contribution of such 249 | Contributor, if any, and such Derivative Works. 250 | 251 | b) Subject to the terms of this Agreement, each Contributor hereby 252 | grants Recipient a non-exclusive, worldwide, royalty-free patent 253 | license under Licensed Patents to make, use, sell, offer to sell, 254 | import and otherwise transfer the Contribution of such Contributor, 255 | if any, in Source Code or other form. This patent license shall 256 | apply to the combination of the Contribution and the Program if, at 257 | the time the Contribution is added by the Contributor, such addition 258 | of the Contribution causes such combination to be covered by the 259 | Licensed Patents. The patent license shall not apply to any other 260 | combinations which include the Contribution. No hardware per se is 261 | licensed hereunder. 262 | 263 | c) Recipient understands that although each Contributor grants the 264 | licenses to its Contributions set forth herein, no assurances are 265 | provided by any Contributor that the Program does not infringe the 266 | patent or other intellectual property rights of any other entity. 267 | Each Contributor disclaims any liability to Recipient for claims 268 | brought by any other entity based on infringement of intellectual 269 | property rights or otherwise. As a condition to exercising the 270 | rights and licenses granted hereunder, each Recipient hereby 271 | assumes sole responsibility to secure any other intellectual 272 | property rights needed, if any. For example, if a third party 273 | patent license is required to allow Recipient to Distribute the 274 | Program, it is Recipient's responsibility to acquire that license 275 | before distributing the Program. 276 | 277 | d) Each Contributor represents that to its knowledge it has 278 | sufficient copyright rights in its Contribution, if any, to grant 279 | the copyright license set forth in this Agreement. 280 | 281 | e) Notwithstanding the terms of any Secondary License, no 282 | Contributor makes additional grants to any Recipient (other than 283 | those set forth in this Agreement) as a result of such Recipient's 284 | receipt of the Program under the terms of a Secondary License 285 | (if permitted under the terms of Section 3). 286 | 287 | 3. REQUIREMENTS 288 | 289 | 3.1 If a Contributor Distributes the Program in any form, then: 290 | 291 | a) the Program must also be made available as Source Code, in 292 | accordance with section 3.2, and the Contributor must accompany 293 | the Program with a statement that the Source Code for the Program 294 | is available under this Agreement, and informs Recipients how to 295 | obtain it in a reasonable manner on or through a medium customarily 296 | used for software exchange; and 297 | 298 | b) the Contributor may Distribute the Program under a license 299 | different than this Agreement, provided that such license: 300 | i) effectively disclaims on behalf of all other Contributors all 301 | warranties and conditions, express and implied, including 302 | warranties or conditions of title and non-infringement, and 303 | implied warranties or conditions of merchantability and fitness 304 | for a particular purpose; 305 | 306 | ii) effectively excludes on behalf of all other Contributors all 307 | liability for damages, including direct, indirect, special, 308 | incidental and consequential damages, such as lost profits; 309 | 310 | iii) does not attempt to limit or alter the recipients' rights 311 | in the Source Code under section 3.2; and 312 | 313 | iv) requires any subsequent distribution of the Program by any 314 | party to be under a license that satisfies the requirements 315 | of this section 3. 316 | 317 | 3.2 When the Program is Distributed as Source Code: 318 | 319 | a) it must be made available under this Agreement, or if the 320 | Program (i) is combined with other material in a separate file or 321 | files made available under a Secondary License, and (ii) the initial 322 | Contributor attached to the Source Code the notice described in 323 | Exhibit A of this Agreement, then the Program may be made available 324 | under the terms of such Secondary Licenses, and 325 | 326 | b) a copy of this Agreement must be included with each copy of 327 | the Program. 328 | 329 | 3.3 Contributors may not remove or alter any copyright, patent, 330 | trademark, attribution notices, disclaimers of warranty, or limitations 331 | of liability ("notices") contained within the Program from any copy of 332 | the Program which they Distribute, provided that Contributors may add 333 | their own appropriate notices. 334 | 335 | 4. COMMERCIAL DISTRIBUTION 336 | 337 | Commercial distributors of software may accept certain responsibilities 338 | with respect to end users, business partners and the like. While this 339 | license is intended to facilitate the commercial use of the Program, 340 | the Contributor who includes the Program in a commercial product 341 | offering should do so in a manner which does not create potential 342 | liability for other Contributors. Therefore, if a Contributor includes 343 | the Program in a commercial product offering, such Contributor 344 | ("Commercial Contributor") hereby agrees to defend and indemnify every 345 | other Contributor ("Indemnified Contributor") against any losses, 346 | damages and costs (collectively "Losses") arising from claims, lawsuits 347 | and other legal actions brought by a third party against the Indemnified 348 | Contributor to the extent caused by the acts or omissions of such 349 | Commercial Contributor in connection with its distribution of the Program 350 | in a commercial product offering. The obligations in this section do not 351 | apply to any claims or Losses relating to any actual or alleged 352 | intellectual property infringement. In order to qualify, an Indemnified 353 | Contributor must: a) promptly notify the Commercial Contributor in 354 | writing of such claim, and b) allow the Commercial Contributor to control, 355 | and cooperate with the Commercial Contributor in, the defense and any 356 | related settlement negotiations. The Indemnified Contributor may 357 | participate in any such claim at its own expense. 358 | 359 | For example, a Contributor might include the Program in a commercial 360 | product offering, Product X. That Contributor is then a Commercial 361 | Contributor. If that Commercial Contributor then makes performance 362 | claims, or offers warranties related to Product X, those performance 363 | claims and warranties are such Commercial Contributor's responsibility 364 | alone. Under this section, the Commercial Contributor would have to 365 | defend claims against the other Contributors related to those performance 366 | claims and warranties, and if a court requires any other Contributor to 367 | pay any damages as a result, the Commercial Contributor must pay 368 | those damages. 369 | 370 | 5. NO WARRANTY 371 | 372 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 373 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" 374 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 375 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 376 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 377 | PURPOSE. Each Recipient is solely responsible for determining the 378 | appropriateness of using and distributing the Program and assumes all 379 | risks associated with its exercise of rights under this Agreement, 380 | including but not limited to the risks and costs of program errors, 381 | compliance with applicable laws, damage to or loss of data, programs 382 | or equipment, and unavailability or interruption of operations. 383 | 384 | 6. DISCLAIMER OF LIABILITY 385 | 386 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 387 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS 388 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 389 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 390 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 391 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 392 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 393 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE 394 | POSSIBILITY OF SUCH DAMAGES. 395 | 396 | 7. GENERAL 397 | 398 | If any provision of this Agreement is invalid or unenforceable under 399 | applicable law, it shall not affect the validity or enforceability of 400 | the remainder of the terms of this Agreement, and without further 401 | action by the parties hereto, such provision shall be reformed to the 402 | minimum extent necessary to make such provision valid and enforceable. 403 | 404 | If Recipient institutes patent litigation against any entity 405 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 406 | Program itself (excluding combinations of the Program with other software 407 | or hardware) infringes such Recipient's patent(s), then such Recipient's 408 | rights granted under Section 2(b) shall terminate as of the date such 409 | litigation is filed. 410 | 411 | All Recipient's rights under this Agreement shall terminate if it 412 | fails to comply with any of the material terms or conditions of this 413 | Agreement and does not cure such failure in a reasonable period of 414 | time after becoming aware of such noncompliance. If all Recipient's 415 | rights under this Agreement terminate, Recipient agrees to cease use 416 | and distribution of the Program as soon as reasonably practicable. 417 | However, Recipient's obligations under this Agreement and any licenses 418 | granted by Recipient relating to the Program shall continue and survive. 419 | 420 | Everyone is permitted to copy and distribute copies of this Agreement, 421 | but in order to avoid inconsistency the Agreement is copyrighted and 422 | may only be modified in the following manner. The Agreement Steward 423 | reserves the right to publish new versions (including revisions) of 424 | this Agreement from time to time. No one other than the Agreement 425 | Steward has the right to modify this Agreement. The Eclipse Foundation 426 | is the initial Agreement Steward. The Eclipse Foundation may assign the 427 | responsibility to serve as the Agreement Steward to a suitable separate 428 | entity. Each new version of the Agreement will be given a distinguishing 429 | version number. The Program (including Contributions) may always be 430 | Distributed subject to the version of the Agreement under which it was 431 | received. In addition, after a new version of the Agreement is published, 432 | Contributor may elect to Distribute the Program (including its 433 | Contributions) under the new version. 434 | 435 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient 436 | receives no rights or licenses to the intellectual property of any 437 | Contributor under this Agreement, whether expressly, by implication, 438 | estoppel or otherwise. All rights in the Program not expressly granted 439 | under this Agreement are reserved. Nothing in this Agreement is intended 440 | to be enforceable by any entity that is not a Contributor or Recipient. 441 | No third-party beneficiary rights are created under this Agreement. 442 | 443 | Exhibit A - Form of Secondary Licenses Notice 444 | 445 | "This Source Code may also be made available under the following 446 | Secondary Licenses when the conditions for such availability set forth 447 | in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), 448 | version(s), and exceptions or additional permissions here}." 449 | 450 | Simply including a copy of this Agreement, including this Exhibit A 451 | is not sufficient to license the Source Code under Secondary Licenses. 452 | 453 | If it is not possible or desirable to put the notice in a particular 454 | file, then You may include the notice in a location (such as a LICENSE 455 | file in a relevant directory) where a recipient would be likely to 456 | look for such a notice. 457 | 458 | You may add additional accurate notices of copyright ownership. 459 | 460 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Discussion](https://img.shields.io/badge/discussion-on%20github-blue)](https://github.com/eclipse-zenoh/roadmap/discussions) 2 | [![Discord](https://img.shields.io/badge/chat-on%20discord-blue)](https://discord.gg/vSDSpqnbkm) 3 | [![Eclipse CI](https://ci.eclipse.org/zenoh/buildStatus/icon?job=zenoh-flow-python-nightly&subject=Eclipse%20CI)](https://ci.eclipse.org/zenoh/view/Zenoh%20Flow/job/zenoh-flow-python-nightly/) 4 | [![CI](https://github.com/eclipse-zenoh/zenoh-flow-python/actions/workflows/ci.yml/badge.svg)](https://github.com/eclipse-zenoh/zenoh-flow-python/actions/workflows/ci.yml) 5 | 6 | 7 | 8 | 9 | # Zenoh-Flow Python bindings 10 | [Zenoh-Flow](https://github.com/eclipse-zenoh-flow/zenoh-flow) provides a Zenoh-based data flow programming framework for computations that span from the cloud to the device. 11 | 12 | :warning: **This software is still in alpha status and we do not recommend using it in production.** 13 | 14 | ----------- 15 | 16 | ## Requirements 17 | 18 | - Rust: see the [installation page](https://www.rust-lang.org/tools/install) 19 | - Python >= 3.8 20 | - pip >= 22 21 | - virtualenv 22 | 23 | 24 | ## Installation 25 | 26 | ### Install the Python package: `zenoh_flow_python` 27 | 28 | If it's not already the case, start by activating a virtual environment: 29 | 30 | ```bash 31 | $ python3 -m virtualenv venv 32 | $ source venv/bin/activate 33 | ``` 34 | 35 | ⚠️ On **macOS** the Zenoh-Flow wrappers have to patch the `sys.path` variable of the Python interpreter with the location of the site-packages folder of the currently active virtual environment. To do so, we rely on the `$VIRTUAL_ENV` environment variable. If your favorite environment manager does not set this variable then the `zenoh_flow_python` module will not be found when launching your flow. 36 | 37 | Build the Python Wheel **within** a Python virtual environment. 38 | 39 | ```bash 40 | (venv) $ git clone https://github.com/eclipse-zenoh-flow/zenoh-flow-python 41 | (venv) $ cd zenoh-flow-python/zenoh-flow-python 42 | (venv) $ pip3 install -r requirements-dev.txt 43 | (venv) $ maturin build --release 44 | (venv) $ pip install ./target/wheels/ 45 | ``` 46 | 47 | #### Build the wrapper shared libraries & generate a configuration for the Zenoh-Flow runtime 48 | 49 | Build the Python wrappers. 50 | 51 | ```bash 52 | $ cargo build --release -p zenoh-flow-python-operator-wrapper -p zenoh-flow-python-sink-wrapper -p zenoh-flow-python-source-wrapper 53 | ``` 54 | 55 | 56 | ## FAQ and Troubleshooting 57 | 58 | ### Cannot load library, no extension found for files of type < py > 59 | 60 | This error indicates that the Zenoh-Flow runtime was not properly configured to support nodes written in Python. 61 | 62 | You need to change the configuration of Zenoh-Flow to let it know how to load Python scripts. 63 | 64 | *If* you launched the Zenoh-Flow runtime in a **standalone** fashion, you need to provide a configuration that contains the following: 65 | 66 | ```yaml 67 | - file_extension: py 68 | libraries: 69 | # Linux 70 | sink: /path/to/zenoh-flow-python/target/release/libzenoh_flow_python_sink_wrapper.so 71 | operator: /path/to/zenoh-flow-python/target/release/libzenoh_flow_python_operator_wrapper.so 72 | source: /path/to/zenoh-flow-python/target/release/libzenoh_flow_python_source_wrapper.so 73 | # macOS 74 | # sink: /path/to/zenoh-flow-python/target/release/libzenoh_flow_python_sink_wrapper.dylib 75 | # operator: /path/to/zenoh-flow-python/target/release/libzenoh_flow_python_operator_wrapper.dylib 76 | # source: /path/to/zenoh-flow-python/target/release/libzenoh_flow_python_source_wrapper.dylib 77 | ``` 78 | 79 | *If* you launched Zenoh-Flow as a **Zenoh plugin**, you need to update the Zenoh configuration with the following: 80 | 81 | ```json5 82 | { 83 | "plugins": { 84 | "zenoh_flow": { 85 | "name": "my-zenoh-flow", 86 | "extensions": [ 87 | { 88 | "file_extension": "py", 89 | "libraries": { 90 | // Linux 91 | "operator": "/path/to/zenoh-flow-python/target/release/libzenoh_flow_python_operator_wrapper.so", 92 | "sink": "/path/to/zenoh-flow-python/target/release/libzenoh_flow_python_sink_wrapper.so", 93 | "source": "/path/to/zenoh-flow-python/target/release/libzenoh_flow_python_source_wrapper.so", 94 | // macOS 95 | // "operator": "/path/to/zenoh-flow-python/target/release/libzenoh_flow_python_operator_wrapper.dylib", 96 | // "sink": "/path/to/zenoh-flow-python/target/release/libzenoh_flow_python_sink_wrapper.dylib", 97 | // "source": "/path/to/zenoh-flow-python/target/release/libzenoh_flow_python_source_wrapper.dylib", 98 | } 99 | } 100 | ] 101 | } 102 | } 103 | } 104 | ``` 105 | 106 | ### Failed to load `zenoh_flow_python` module 107 | 108 | First, check that you have activated your virtual environment before launching the Zenoh-Flow runtime (be it through the standalone daemon, runtime or dedicated Zenoh plugin). **Make sure that the environment variable `$VIRTUAL_ENV` is set**. 109 | 110 | ⚠️ *If your environment manager does not set this variable, please open an issue specifying your setup*. 111 | 112 | If your virtual environment is activated, check that the Zenoh-Flow Python package is indeed installed: 113 | 1. Open a terminal. 114 | 2. Enter the Python interpreter: 115 | ```bash 116 | python 117 | ``` 118 | 3. Try to import Zenoh-Flow Python: 119 | ```bash 120 | import zenoh_flow_python 121 | ``` 122 | 123 | If you still have the above error when launching your data flow, try reinstalling the package: 124 | 1. Open a terminal. 125 | 2. `cd` into `zenoh-flow-python/zenoh-flow-python`. 126 | 3. Build: 127 | ```bash 128 | maturin build --release 129 | ``` 130 | 4. Install: 131 | ```bash 132 | pip install ../target/wheels/*.whl --force-reinstall 133 | ``` 134 | 135 | Try again relaunching your data flow. 136 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2022 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # ZettaScale Zenoh Team, 13 | # 14 | 15 | 16 | # Configuration file for the Sphinx documentation builder. 17 | # 18 | # This file only contains a selection of the most common options. For a full 19 | # list see the documentation: 20 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 21 | 22 | # -- Project setup -------------------------------------------------------------- 23 | 24 | # NOTE: as zenoh-python is developped in Rust, it must be compiled and install in Python environment 25 | # in order to be imported here (and the Sphinx autodoc extension to load the doc from the compiled code) 26 | # For readthedocs.org, zenoh-python is added in docs/requirements.txt so it get it from pypi.org 27 | import zenoh_flow 28 | 29 | # -- Project information ----------------------------------------------------- 30 | 31 | project = 'zenoh-flow' 32 | copyright = '2022, ZettaScale Technology' 33 | author = 'ZettaScale Zenoh Team, ' 34 | 35 | # The full version, including alpha/beta/rc tags 36 | release = '0.2.0-dev' 37 | 38 | 39 | # -- General configuration --------------------------------------------------- 40 | 41 | # Add any Sphinx extension module names here, as strings. They can be 42 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 43 | # ones. 44 | extensions = [ 45 | 'sphinx.ext.autodoc', 46 | 'sphinx.ext.autosummary', 47 | 'sphinx.ext.intersphinx', 48 | 'sphinx_rtd_theme' 49 | ] 50 | 51 | # Add any paths that contain templates here, relative to this directory. 52 | templates_path = ['_templates'] 53 | 54 | # The suffix(es) of source filenames. 55 | # You can specify multiple suffix as a list of string: 56 | # 57 | # source_suffix = ['.rst', '.md'] 58 | source_suffix = '.rst' 59 | 60 | # The master toctree document. 61 | master_doc = 'index' 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | # 66 | # This is also used if you do content translation via gettext catalogs. 67 | # Usually you set "language" from the command line for these cases. 68 | #language = 'python' 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | # This pattern also affects html_static_path and html_extra_path. 73 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 74 | 75 | # The name of the Pygments (syntax highlighting) style to use. 76 | pygments_style = None 77 | 78 | autodoc_mock_imports = ['zenoh_flow'] 79 | 80 | # -- Options for HTML output ------------------------------------------------- 81 | 82 | # The theme to use for HTML and HTML Help pages. See the documentation for 83 | # a list of builtin themes. 84 | # 85 | html_theme = 'sphinx_rtd_theme' 86 | 87 | # Add any paths that contain custom static files (such as style sheets) here, 88 | # relative to this directory. They are copied after the builtin static files, 89 | # so a file named "default.css" will overwrite the builtin "default.css". 90 | # html_static_path = ['_static'] 91 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. 2 | .. Copyright (c) 2022 ZettaScale Technology 3 | .. 4 | .. This program and the accompanying materials are made available under the 5 | .. terms of the Eclipse Public License 2.0 which is available at 6 | .. http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | .. which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | .. 9 | .. SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | .. 11 | .. Contributors: 12 | .. ZettaScale Zenoh Team, 13 | .. 14 | 15 | ================= 16 | zenoh-flow-python 17 | ================= 18 | 19 | 20 | Zenoh Flow 21 | ---------- 22 | .. automodule:: zenoh_flow 23 | :members: 24 | 25 | 26 | 27 | 28 | 29 | 30 | .. toctree:: 31 | :maxdepth: 1 32 | 33 | Zenoh Flow Interfaces 34 | Zenoh Flow API 35 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | ### NOTE: Read the Docs cannot build the Rust code, and thus is not able to import zenoh 2 | ### unless it's installed from pypi.org as a requirement 3 | eclipse-zenoh-flow == 0.4.0 4 | -------------------------------------------------------------------------------- /docs/zenoh-flow-api.rst: -------------------------------------------------------------------------------- 1 | .. 2 | .. Copyright (c) 2022 ZettaScale Technology 3 | .. 4 | .. This program and the accompanying materials are made available under the 5 | .. terms of the Eclipse Public License 2.0 which is available at 6 | .. http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | .. which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | .. 9 | .. SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | .. 11 | .. Contributors: 12 | .. ZettaScale Zenoh Team, 13 | .. 14 | 15 | Zenoh Flow API Reference 16 | ======================== 17 | 18 | Output 19 | ---------- 20 | .. autoclass:: zenoh_flow.Output 21 | :members: 22 | 23 | Input 24 | ------------ 25 | .. autoclass:: zenoh_flow.Input 26 | :members: 27 | 28 | Message 29 | ----------- 30 | .. autoclass:: zenoh_flow.Message 31 | :members: 32 | 33 | 34 | Outputs 35 | ---------- 36 | .. autoclass:: zenoh_flow.types.Outputs 37 | :members: 38 | 39 | Inputs 40 | ------------ 41 | .. autoclass:: zenoh_flow.types.Inputs 42 | :members: 43 | 44 | Context 45 | ------- 46 | .. autoclass:: zenoh_flow.types.Context 47 | :members: 48 | 49 | Timestamp 50 | --------- 51 | .. autoclass:: zenoh_flow.types.Timestamp 52 | :members: 53 | 54 | 55 | RawOutput 56 | ---------- 57 | .. autoclass:: zenoh_flow.RawOutput 58 | :members: 59 | 60 | RawInput 61 | ------------ 62 | .. autoclass:: zenoh_flow.RawInput 63 | :members: 64 | 65 | 66 | RawMessage 67 | -------------- 68 | .. autoclass:: zenoh_flow.RawMessage 69 | :members: 70 | -------------------------------------------------------------------------------- /docs/zenoh-flow-nodes.rst: -------------------------------------------------------------------------------- 1 | .. 2 | .. Copyright (c) 2022 ZettaScale Technology 3 | .. 4 | .. This program and the accompanying materials are made available under the 5 | .. terms of the Eclipse Public License 2.0 which is available at 6 | .. http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | .. which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | .. 9 | .. SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | .. 11 | .. Contributors: 12 | .. ZettaScale Zenoh Team, 13 | .. 14 | 15 | 16 | Zenoh Flow interfaces references 17 | ================================ 18 | 19 | Note that, this API is NOT meant to be used directly. 20 | Instead your operators, sink and sources MUST implement the methods provided by the following classes. 21 | Only then, such nodes can to be loaded by a Zenoh Flow Runtime 22 | 23 | Source 24 | ------ 25 | .. autoclass:: zenoh_flow.interfaces.Source 26 | :members: 27 | 28 | 29 | Sink 30 | ---- 31 | .. autoclass:: zenoh_flow.interfaces.Sink 32 | :members: 33 | 34 | 35 | Operator 36 | -------- 37 | .. autoclass:: zenoh_flow.interfaces.Operator 38 | :members: 39 | -------------------------------------------------------------------------------- /examples/flow.yaml: -------------------------------------------------------------------------------- 1 | name: zenoh-flow-python 2 | 3 | vars: 4 | BASE_DIR: "/path/to/zenoh-flow-python/examples" 5 | 6 | 7 | sources: 8 | - id: zenoh-source 9 | description: "A Zenoh builtin Source" 10 | zenoh-subscribers: 11 | "out": "zenoh-flow-python/source" 12 | 13 | 14 | sinks: 15 | - id: python-sink 16 | library: "file://{{ BASE_DIR }}/sink.py" 17 | configuration: 18 | test: configuration-test 19 | inputs: 20 | - in 21 | 22 | 23 | links: 24 | - from: 25 | node: zenoh-source 26 | output: out 27 | to: 28 | node: python-sink 29 | input: in 30 | -------------------------------------------------------------------------------- /examples/sink.py: -------------------------------------------------------------------------------- 1 | from asyncio import * 2 | from typing import Any, Dict 3 | import logging 4 | logging.getLogger().setLevel(0) 5 | 6 | import sys 7 | logging.debug(sys.path) 8 | 9 | from zenoh_flow_python import * 10 | 11 | class MySink(Sink): 12 | input: InputRaw 13 | 14 | def __init__(self, context: Context, configuration: Dict[str, Any], inputs: Inputs): 15 | logging.info(configuration["test"]) 16 | self.input = inputs.take_raw("in") 17 | 18 | 19 | async def iteration(self) -> None: 20 | message = await self.input.recv_async() 21 | logging.info("timestamp: {}, payload: {}".format(message.timestamp(), message.payload())) 22 | 23 | 24 | def finalize(self) -> None: 25 | pass 26 | 27 | 28 | def register(): 29 | return MySink 30 | -------------------------------------------------------------------------------- /examples/zenoh-flow-configuration.yaml: -------------------------------------------------------------------------------- 1 | - file_extension: py 2 | libraries: 3 | sink: /Users/julien/dev/zenoh-flow-python/target/debug/libzenoh_flow_python_sink_wrapper.dylib 4 | source: /Users/julien/dev/zenoh-flow-python/target/debug/libzenoh_flow_python_source_wrapper.dylib 5 | operator: /Users/julien/dev/zenoh-flow-python/target/debug/libzenoh_flow_python_operator_wrapper.dylib 6 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.72.1" 3 | -------------------------------------------------------------------------------- /zenoh-flow-python-operator-wrapper/Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright © 2022 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # ZettaScale Zenoh Team, 13 | # 14 | 15 | [package] 16 | authors = { workspace = true } 17 | categories = { workspace = true } 18 | description = "Python wrapper for a Zenoh-Flow Operator node." 19 | edition = { workspace = true } 20 | homepage = { workspace = true } 21 | license = { workspace = true } 22 | name = "zenoh-flow-python-operator-wrapper" 23 | repository = { workspace = true } 24 | version = { workspace = true } 25 | 26 | [dependencies] 27 | async-trait.workspace = true 28 | pyo3.workspace = true 29 | pyo3-asyncio.workspace = true 30 | pyo3-pylogger.workspace = true 31 | tracing.workspace = true 32 | tracing-subscriber.workspace = true 33 | zenoh-flow-python.workspace = true 34 | zenoh-flow-nodes.workspace = true 35 | 36 | [lib] 37 | crate-type = ["cdylib"] 38 | -------------------------------------------------------------------------------- /zenoh-flow-python-operator-wrapper/src/lib.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2022 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // ZettaScale Zenoh Team, 13 | // 14 | 15 | use std::sync::Arc; 16 | 17 | use async_trait::async_trait; 18 | use pyo3::{types::PyModule, PyAny, Python}; 19 | use zenoh_flow_nodes::{ 20 | prelude as zf, 21 | prelude::{anyhow, export_operator, Operator}, 22 | }; 23 | use zenoh_flow_python::{configuration_into_py, Context, Inputs, Outputs, PythonState}; 24 | 25 | #[export_operator] 26 | struct ZenohFlowPythonOperator { 27 | state: Arc, 28 | } 29 | 30 | #[async_trait] 31 | impl Operator for ZenohFlowPythonOperator { 32 | async fn new( 33 | context: zf::Context, 34 | configuration: zf::Configuration, 35 | inputs: zf::Inputs, 36 | outputs: zf::Outputs, 37 | ) -> zf::Result { 38 | pyo3_pylogger::register(&format!( 39 | "zenoh_flow_python_operator::{}", 40 | context.node_id() 41 | )); 42 | let _ = tracing_subscriber::fmt::try_init(); 43 | 44 | let state = Arc::new(Python::with_gil(|py| { 45 | // NOTE: See https://github.com/PyO3/pyo3/issues/1741#issuecomment-1191125053 46 | // 47 | // On macOS, the site-packages folder of the current virtual environment is not added to the `sys.path` 48 | // making it impossible to load the modules that were installed on it. 49 | #[cfg(target_os = "macos")] 50 | if let Ok(venv) = std::env::var("VIRTUAL_ENV") { 51 | let version_info = py.version_info(); 52 | let sys = py.import("sys").unwrap(); 53 | let sys_path = sys.getattr("path").unwrap(); 54 | let site_packages_dir = format!( 55 | "{}/lib/python{}.{}/site-packages", 56 | venv, version_info.major, version_info.minor 57 | ); 58 | 59 | tracing::debug!("Adding virtual environment site-packages folder to Python interpreter path: {site_packages_dir}"); 60 | 61 | sys_path 62 | .call_method1("append", (site_packages_dir,)) 63 | .unwrap(); 64 | } 65 | 66 | let py_configuration = configuration_into_py(py, configuration) 67 | .map_err(|e| anyhow!("Failed to convert `Configuration` to `PyObject`: {e:?}"))?; 68 | 69 | // Setting asyncio event loop 70 | let asyncio = py.import("asyncio").unwrap(); 71 | let event_loop = asyncio.call_method0("new_event_loop").unwrap(); 72 | asyncio 73 | .call_method1("set_event_loop", (event_loop,)) 74 | .unwrap(); 75 | let task_locals = pyo3_asyncio::TaskLocals::new(event_loop); 76 | 77 | let user_code = std::fs::read_to_string(context.library_path()).map_err(|e| { 78 | anyhow!( 79 | "Failed to read < {} >: {e:?}", 80 | context.library_path().display() 81 | ) 82 | })?; 83 | 84 | let python_module = PyModule::from_code( 85 | py, 86 | &user_code, 87 | &context.library_path().to_string_lossy(), 88 | "zenoh_flow_python_operator", 89 | ) 90 | .map_err(|e| { 91 | anyhow!( 92 | "Failed to create `PyModule` from script < {} >: {e:?}", 93 | context.library_path().display() 94 | ) 95 | })?; 96 | 97 | let operator_class = python_module 98 | .call_method0("register") 99 | .map_err(|e| anyhow!("Call to `register` failed with: {e:?}"))?; 100 | 101 | // NOTE: `call1` will call the object pointed at by `sink_class` with the provided parameters. This 102 | // translates to creating a new instance of the class. 103 | let operator_instance = operator_class 104 | .call1(( 105 | Context::from(context), 106 | py_configuration, 107 | Inputs::from(inputs), 108 | Outputs::from(outputs), 109 | )) 110 | .map_err(|e| anyhow!("Failed to create an Operator instance: {e:?}"))?; 111 | 112 | zf::Result::Ok(PythonState { 113 | node_instance: Arc::new(operator_instance.into()), 114 | task_locals: Arc::new(task_locals), 115 | }) 116 | })?); 117 | 118 | Ok(Self { state }) 119 | } 120 | } 121 | 122 | #[async_trait] 123 | impl zf::Node for ZenohFlowPythonOperator { 124 | async fn iteration(&self) -> zf::Result<()> { 125 | tracing::debug!("iteration"); 126 | 127 | Python::with_gil(|py| { 128 | let operator_instance = self 129 | .state 130 | .node_instance 131 | .downcast::(py) 132 | .map_err(|e| anyhow!("Failed to downcast Operator instance to `PyAny`: {e:?}"))?; 133 | 134 | let iteration_coroutine = operator_instance 135 | .call_method0("iteration") 136 | .map_err(|e| anyhow!("Call to `iteration` failed with: {e:?}"))?; 137 | 138 | let iteration = 139 | pyo3_asyncio::into_future_with_locals(&self.state.task_locals, iteration_coroutine) 140 | .map_err(|e| { 141 | anyhow!( 142 | "(pyo3-asyncio) Failed to transform Python coroutine to Rust future: {e:?}" 143 | ) 144 | })?; 145 | 146 | let _ = pyo3_asyncio::async_std::run_until_complete( 147 | self.state.task_locals.event_loop(py), 148 | iteration, 149 | ) 150 | .map_err(|e| anyhow!("Iteration failed with: {e:?}"))?; 151 | 152 | Ok(()) 153 | }) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /zenoh-flow-python-sink-wrapper/Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright © 2022 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # ZettaScale Zenoh Team, 13 | # 14 | 15 | [package] 16 | authors = { workspace = true } 17 | categories = { workspace = true } 18 | description = "Python wrapper for a Zenoh-Flow Sink node." 19 | edition = { workspace = true } 20 | homepage = { workspace = true } 21 | license = { workspace = true } 22 | name = "zenoh-flow-python-sink-wrapper" 23 | repository = { workspace = true } 24 | version = { workspace = true } 25 | 26 | [dependencies] 27 | async-trait.workspace = true 28 | pyo3.workspace = true 29 | pyo3-asyncio.workspace = true 30 | pyo3-pylogger.workspace = true 31 | tracing.workspace = true 32 | tracing-subscriber.workspace = true 33 | zenoh-flow-python.workspace = true 34 | zenoh-flow-nodes.workspace = true 35 | 36 | [lib] 37 | crate-type = ["cdylib"] 38 | -------------------------------------------------------------------------------- /zenoh-flow-python-sink-wrapper/src/lib.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2022 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // ZettaScale Zenoh Team, 13 | // 14 | 15 | use std::sync::Arc; 16 | 17 | use async_trait::async_trait; 18 | use pyo3::{types::PyModule, PyAny, Python}; 19 | use zenoh_flow_nodes::{ 20 | prelude as zf, 21 | prelude::{anyhow, export_sink, Sink}, 22 | }; 23 | use zenoh_flow_python::{configuration_into_py, Context, Inputs, PythonState}; 24 | 25 | #[export_sink] 26 | struct ZenohFlowPythonSink { 27 | state: Arc, 28 | } 29 | 30 | #[async_trait] 31 | impl Sink for ZenohFlowPythonSink { 32 | async fn new( 33 | context: zf::Context, 34 | configuration: zf::Configuration, 35 | inputs: zf::Inputs, 36 | ) -> zf::Result { 37 | pyo3_pylogger::register(&format!("zenoh_flow_python_sink::{}", context.node_id())); 38 | let _ = tracing_subscriber::fmt::try_init(); 39 | 40 | let state = Arc::new(Python::with_gil(|py| { 41 | // NOTE: See https://github.com/PyO3/pyo3/issues/1741#issuecomment-1191125053 42 | // 43 | // On macOS, the site-packages folder of the current virtual environment is not added to the `sys.path` 44 | // making it impossible to load the modules that were installed on it. 45 | #[cfg(target_os = "macos")] 46 | if let Ok(venv) = std::env::var("VIRTUAL_ENV") { 47 | let version_info = py.version_info(); 48 | let sys = py.import("sys").unwrap(); 49 | let sys_path = sys.getattr("path").unwrap(); 50 | let site_packages_dir = format!( 51 | "{}/lib/python{}.{}/site-packages", 52 | venv, version_info.major, version_info.minor 53 | ); 54 | 55 | tracing::debug!("Adding virtual environment site-packages folder to Python interpreter path: {site_packages_dir}"); 56 | 57 | sys_path 58 | .call_method1("append", (site_packages_dir,)) 59 | .unwrap(); 60 | } 61 | 62 | let py_configuration = configuration_into_py(py, configuration) 63 | .map_err(|e| anyhow!("Failed to convert `Configuration` to `PyObject`: {e:?}"))?; 64 | 65 | // Setting asyncio event loop 66 | let asyncio = py.import("asyncio").unwrap(); 67 | let event_loop = asyncio.call_method0("new_event_loop").unwrap(); 68 | asyncio 69 | .call_method1("set_event_loop", (event_loop,)) 70 | .unwrap(); 71 | let task_locals = pyo3_asyncio::TaskLocals::new(event_loop); 72 | 73 | let user_code = std::fs::read_to_string(context.library_path()).map_err(|e| { 74 | anyhow!( 75 | "Failed to read < {} >: {e:?}", 76 | context.library_path().display() 77 | ) 78 | })?; 79 | 80 | let python_module = PyModule::from_code( 81 | py, 82 | &user_code, 83 | &context.library_path().to_string_lossy(), 84 | "zenoh_flow_python_sink", 85 | ) 86 | .map_err(|e| { 87 | anyhow!( 88 | "Failed to create `PyModule` from script < {} >: {e:?}", 89 | context.library_path().display() 90 | ) 91 | })?; 92 | 93 | let sink_class = python_module 94 | .call_method0("register") 95 | .map_err(|e| anyhow!("Call to `register` failed with: {e:?}"))?; 96 | 97 | // NOTE: `call1` will call the object pointed at by `sink_class` with the provided parameters. This 98 | // translates to creating a new instance of the class. 99 | let sink_instance = sink_class 100 | .call1(( 101 | Context::from(context), 102 | py_configuration, 103 | Inputs::from(inputs), 104 | )) 105 | .map_err(|e| anyhow!("Failed to create a Sink instance: {e:?}"))?; 106 | 107 | zf::Result::Ok(PythonState { 108 | node_instance: Arc::new(sink_instance.into()), 109 | task_locals: Arc::new(task_locals), 110 | }) 111 | })?); 112 | 113 | Ok(Self { state }) 114 | } 115 | } 116 | 117 | #[async_trait] 118 | impl zf::Node for ZenohFlowPythonSink { 119 | async fn iteration(&self) -> zf::Result<()> { 120 | tracing::debug!("iteration"); 121 | 122 | Python::with_gil(|py| { 123 | let sink_instance = self 124 | .state 125 | .node_instance 126 | .downcast::(py) 127 | .map_err(|e| anyhow!("Failed to downcast Sink instance to `PyAny`: {e:?}"))?; 128 | 129 | let iteration_coroutine = sink_instance 130 | .call_method0("iteration") 131 | .map_err(|e| anyhow!("Call to `iteration` failed with: {e:?}"))?; 132 | 133 | let iteration = 134 | pyo3_asyncio::into_future_with_locals(&self.state.task_locals, iteration_coroutine) 135 | .map_err(|e| { 136 | anyhow!( 137 | "(pyo3-asyncio) Failed to transform Python coroutine to Rust future: {e:?}" 138 | ) 139 | })?; 140 | 141 | let _ = pyo3_asyncio::async_std::run_until_complete( 142 | self.state.task_locals.event_loop(py), 143 | iteration, 144 | ) 145 | .map_err(|e| anyhow!("Iteration failed with: {e:?}"))?; 146 | 147 | Ok(()) 148 | }) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /zenoh-flow-python-source-wrapper/Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright © 2022 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # ZettaScale Zenoh Team, 13 | # 14 | 15 | [package] 16 | authors = { workspace = true } 17 | categories = { workspace = true } 18 | description = "Python wrapper for a Zenoh-Flow Source node." 19 | edition = { workspace = true } 20 | homepage = { workspace = true } 21 | license = { workspace = true } 22 | name = "zenoh-flow-python-source-wrapper" 23 | repository = { workspace = true } 24 | version = { workspace = true } 25 | 26 | [dependencies] 27 | async-trait.workspace = true 28 | pyo3.workspace = true 29 | pyo3-asyncio.workspace = true 30 | pyo3-pylogger.workspace = true 31 | tracing.workspace = true 32 | tracing-subscriber.workspace = true 33 | zenoh-flow-python.workspace = true 34 | zenoh-flow-nodes.workspace = true 35 | 36 | [lib] 37 | crate-type = ["cdylib"] 38 | -------------------------------------------------------------------------------- /zenoh-flow-python-source-wrapper/src/lib.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2022 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // ZettaScale Zenoh Team, 13 | // 14 | 15 | use std::sync::Arc; 16 | 17 | use async_trait::async_trait; 18 | use pyo3::{types::PyModule, PyAny, Python}; 19 | use zenoh_flow_nodes::{ 20 | prelude as zf, 21 | prelude::{anyhow, export_source, Source}, 22 | }; 23 | use zenoh_flow_python::{configuration_into_py, Context, Outputs, PythonState}; 24 | 25 | #[export_source] 26 | struct ZenohFlowPythonSource { 27 | state: Arc, 28 | } 29 | 30 | #[async_trait] 31 | impl Source for ZenohFlowPythonSource { 32 | async fn new( 33 | context: zf::Context, 34 | configuration: zf::Configuration, 35 | outputs: zf::Outputs, 36 | ) -> zf::Result { 37 | pyo3_pylogger::register(&format!("zenoh_flow_python_source::{}", context.node_id())); 38 | let _ = tracing_subscriber::fmt::try_init(); 39 | 40 | let state = Arc::new(Python::with_gil(|py| { 41 | // NOTE: See https://github.com/PyO3/pyo3/issues/1741#issuecomment-1191125053 42 | // 43 | // On macOS, the site-packages folder of the current virtual environment is not added to the `sys.path` 44 | // making it impossible to load the modules that were installed on it. 45 | #[cfg(target_os = "macos")] 46 | if let Ok(venv) = std::env::var("VIRTUAL_ENV") { 47 | let version_info = py.version_info(); 48 | let sys = py.import("sys").unwrap(); 49 | let sys_path = sys.getattr("path").unwrap(); 50 | let site_packages_dir = format!( 51 | "{}/lib/python{}.{}/site-packages", 52 | venv, version_info.major, version_info.minor 53 | ); 54 | 55 | tracing::debug!("Adding virtual environment site-packages folder to Python interpreter path: {site_packages_dir}"); 56 | 57 | sys_path 58 | .call_method1("append", (site_packages_dir,)) 59 | .unwrap(); 60 | } 61 | 62 | let py_configuration = configuration_into_py(py, configuration) 63 | .map_err(|e| anyhow!("Failed to convert `Configuration` to `PyObject`: {e:?}"))?; 64 | 65 | // Setting asyncio event loop 66 | let asyncio = py.import("asyncio").unwrap(); 67 | let event_loop = asyncio.call_method0("new_event_loop").unwrap(); 68 | asyncio 69 | .call_method1("set_event_loop", (event_loop,)) 70 | .unwrap(); 71 | let task_locals = pyo3_asyncio::TaskLocals::new(event_loop); 72 | 73 | let user_code = std::fs::read_to_string(context.library_path()).map_err(|e| { 74 | anyhow!( 75 | "Failed to read < {} >: {e:?}", 76 | context.library_path().display() 77 | ) 78 | })?; 79 | 80 | let python_module = PyModule::from_code( 81 | py, 82 | &user_code, 83 | &context.library_path().to_string_lossy(), 84 | "zenoh_flow_python_source", 85 | ) 86 | .map_err(|e| { 87 | anyhow!( 88 | "Failed to create `PyModule` from script < {} >: {e:?}", 89 | context.library_path().display() 90 | ) 91 | })?; 92 | 93 | let source_class = python_module 94 | .call_method0("register") 95 | .map_err(|e| anyhow!("Call to `register` failed with: {e:?}"))?; 96 | 97 | // NOTE: `call1` will call the object pointed at by `source_class` with the provided parameters. This 98 | // translates to creating a new instance of the class. 99 | let source_instance = source_class 100 | .call1(( 101 | Context::from(context), 102 | py_configuration, 103 | Outputs::from(outputs), 104 | )) 105 | .map_err(|e| anyhow!("Failed to create a Source instance: {e:?}"))?; 106 | 107 | zf::Result::Ok(PythonState { 108 | node_instance: Arc::new(source_instance.into()), 109 | task_locals: Arc::new(task_locals), 110 | }) 111 | })?); 112 | 113 | Ok(Self { state }) 114 | } 115 | } 116 | 117 | #[async_trait] 118 | impl zf::Node for ZenohFlowPythonSource { 119 | async fn iteration(&self) -> zf::Result<()> { 120 | tracing::debug!("iteration"); 121 | 122 | Python::with_gil(|py| { 123 | let source_instance = self 124 | .state 125 | .node_instance 126 | .downcast::(py) 127 | .map_err(|e| anyhow!("Failed to downcast Source instance to `PyAny`: {e:?}"))?; 128 | 129 | let iteration_coroutine = source_instance 130 | .call_method0("iteration") 131 | .map_err(|e| anyhow!("Call to `iteration` failed with: {e:?}"))?; 132 | 133 | let iteration = 134 | pyo3_asyncio::into_future_with_locals(&self.state.task_locals, iteration_coroutine) 135 | .map_err(|e| { 136 | anyhow!( 137 | "(pyo3-asyncio) Failed to transform Python coroutine to Rust future: {e:?}" 138 | ) 139 | })?; 140 | 141 | let _ = pyo3_asyncio::async_std::run_until_complete( 142 | self.state.task_locals.event_loop(py), 143 | iteration, 144 | ) 145 | .map_err(|e| anyhow!("Iteration failed with: {e:?}"))?; 146 | 147 | Ok(()) 148 | }) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /zenoh-flow-python/Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright © 2022 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # ZettaScale Zenoh Team, 13 | # 14 | 15 | [package] 16 | authors = { workspace = true } 17 | categories = { workspace = true } 18 | description = "Zenoh-Flow Python bindings core library." 19 | edition = { workspace = true } 20 | homepage = { workspace = true } 21 | license = { workspace = true } 22 | name = "zenoh-flow-python" 23 | repository = { workspace = true } 24 | version = { workspace = true } 25 | 26 | [dependencies] 27 | anyhow = { workspace = true } 28 | pyo3 = { workspace = true } 29 | pyo3-asyncio = { workspace = true } 30 | serde_json = { workspace = true } 31 | tracing.workspace = true 32 | zenoh-flow-nodes = { workspace = true } 33 | 34 | [features] 35 | extension-module = ["pyo3/extension-module"] 36 | 37 | [lib] 38 | name = "zenoh_flow_python" 39 | crate-type = ["cdylib", "rlib"] 40 | -------------------------------------------------------------------------------- /zenoh-flow-python/pyproject.toml: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Copyright © 2022 ZettaScale Technology 4 | # 5 | # This program and the accompanying materials are made available under the 6 | # terms of the Eclipse Public License 2.0 which is available at 7 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 8 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 9 | # 10 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 11 | # 12 | # Contributors: 13 | # ZettaScale Zenoh Team, 14 | # 15 | 16 | [build-system] 17 | requires = ["maturin>=1.5,<2.0"] 18 | build-backend = "maturin" 19 | 20 | [project] 21 | author = "ZettaScale Zenoh team" 22 | author_email = "zenoh@zettascale.tech" 23 | classifiers = [ 24 | "Programming Language :: Python :: 3.8", 25 | "Programming Language :: Python :: 3.9", 26 | "Programming Language :: Python :: 3.10", 27 | "Programming Language :: Python :: 3.11", 28 | "Programming Language :: Python :: 3.12", 29 | "Programming Language :: Rust", 30 | "Programming Language :: Python :: Implementation :: CPython", 31 | "Programming Language :: Python :: Implementation :: PyPy", 32 | "Intended Audience :: Developers", 33 | "Development Status :: 3 - Alpha", 34 | "License :: OSI Approved :: Apache Software License", 35 | "License :: OSI Approved :: Eclipse Public License 2.0 (EPL-2.0)", 36 | "Operating System :: POSIX :: Linux", 37 | "Operating System :: MacOS :: MacOS X", 38 | ] 39 | description = "Eclipse Zenoh-Flow Python bindings." 40 | dynamic = ["version"] 41 | name = "zenoh-flow-python" 42 | requires-python = ">=3.8" 43 | 44 | [tool.maturin] 45 | features = ["pyo3/extension-module"] 46 | 47 | [project.urls] 48 | "Bug Tracker" = "https://github.com/eclipse-zenoh-flow/zenoh-flow-python/issues" 49 | "Source Code" = "https://github.com/eclipse-zenoh-flow/zenoh-flow-python" 50 | -------------------------------------------------------------------------------- /zenoh-flow-python/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # The version range of `maturin` should be the same as in pyproject.toml 2 | maturin>=1.5,<2.0 3 | -------------------------------------------------------------------------------- /zenoh-flow-python/src/lib.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // ZettaScale Zenoh Team, 13 | // 14 | 15 | use std::{fmt::Display, path::PathBuf, sync::Arc}; 16 | 17 | use anyhow::anyhow; 18 | use pyo3::{ 19 | exceptions::{PyTypeError, PyValueError}, 20 | prelude::*, 21 | types::{PyDict, PyList}, 22 | PyErr, PyObject, PyResult, Python, ToPyObject, 23 | }; 24 | use pyo3_asyncio::TaskLocals; 25 | use serde_json::Value; 26 | use zenoh_flow_nodes::prelude as zf; 27 | 28 | #[derive(Clone)] 29 | pub struct PythonState { 30 | pub node_instance: Arc, 31 | pub task_locals: Arc, 32 | } 33 | 34 | impl Drop for PythonState { 35 | fn drop(&mut self) { 36 | Python::with_gil(|py| { 37 | self.node_instance 38 | .call_method0(py, "finalize") 39 | .expect("Failed to call `finalize` on the internal Python state"); 40 | }); 41 | } 42 | } 43 | 44 | #[pymodule] 45 | fn zenoh_flow_python(_py: Python<'_>, m: &PyModule) -> PyResult<()> { 46 | m.add_class::()?; 47 | m.add_class::()?; 48 | m.add_class::()?; 49 | m.add_class::()?; 50 | m.add_class::()?; 51 | m.add_class::()?; 52 | m.add_class::()?; 53 | Ok(()) 54 | } 55 | 56 | #[pyo3::pyclass] 57 | #[derive(Debug)] 58 | pub struct InstanceId(pub(crate) zf::InstanceId); 59 | 60 | impl From<&zf::InstanceId> for InstanceId { 61 | fn from(value: &zf::InstanceId) -> Self { 62 | Self(value.clone()) 63 | } 64 | } 65 | 66 | impl Display for InstanceId { 67 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 68 | write!(f, "{}", self.0.simple()) 69 | } 70 | } 71 | 72 | #[pyo3::pyclass] 73 | #[derive(Debug)] 74 | pub struct RuntimeId(pub(crate) zf::RuntimeId); 75 | 76 | impl From<&zf::RuntimeId> for RuntimeId { 77 | fn from(value: &zf::RuntimeId) -> Self { 78 | Self(value.clone()) 79 | } 80 | } 81 | 82 | impl Display for RuntimeId { 83 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 84 | write!(f, "{}", self.0) 85 | } 86 | } 87 | 88 | #[pyo3::pyclass] 89 | pub struct Context(pub(crate) zf::Context); 90 | 91 | impl From for Context { 92 | fn from(value: zf::Context) -> Self { 93 | Self(value) 94 | } 95 | } 96 | 97 | #[pyo3::pymethods] 98 | impl Context { 99 | pub fn data_flow_name(&self) -> &str { 100 | self.0.name() 101 | } 102 | 103 | pub fn data_flow_instance_id(&self) -> InstanceId { 104 | self.0.instance_id().into() 105 | } 106 | 107 | pub fn runtime_id(&self) -> RuntimeId { 108 | self.0.runtime_id().into() 109 | } 110 | 111 | pub fn library_path(&self) -> &PathBuf { 112 | self.0.library_path() 113 | } 114 | 115 | pub fn node_id(&self) -> &str { 116 | self.0.node_id() 117 | } 118 | } 119 | 120 | #[pyo3::pyclass] 121 | pub struct Timestamp(pub(crate) zf::Timestamp); 122 | 123 | impl From<&zf::Timestamp> for Timestamp { 124 | fn from(value: &zf::Timestamp) -> Self { 125 | Timestamp(*value) 126 | } 127 | } 128 | 129 | #[pyo3::pymethods] 130 | impl Timestamp { 131 | fn time(&self) -> u64 { 132 | // NOTE: u64::MAX, if converted from **nanoseconds** to a timestamp, will cease to function in 2554. Given we're 133 | // converting to **milliseconds** we should be safe. 134 | self.0.get_time().to_duration().as_millis() as u64 135 | } 136 | 137 | fn id(&self) -> String { 138 | self.0.get_id().to_string() 139 | } 140 | } 141 | 142 | #[pyo3::pyclass] 143 | pub struct LinkMessage { 144 | pub(crate) message: zf::LinkMessage, 145 | pub(crate) serialised_payload: Vec, 146 | } 147 | 148 | impl From for LinkMessage { 149 | fn from(value: zf::LinkMessage) -> Self { 150 | LinkMessage { 151 | message: value, 152 | serialised_payload: Vec::default(), 153 | } 154 | } 155 | } 156 | 157 | #[pyo3::pymethods] 158 | impl LinkMessage { 159 | pub fn payload(&mut self) -> PyResult<&[u8]> { 160 | match self.message.payload() { 161 | // TODO As pointed by @gabrik, in a previous version of Zenoh-Flow the call to the 162 | // method `as_slice` was replaced with another approach because, performance-wise, 163 | // it was vastly inferior. 164 | // 165 | // Given that pyO3 changed quite a bit between these versions, we ought to check 166 | // if that assumption still holds. 167 | zf::Payload::Bytes(bytes) => Ok(bytes.as_slice()), 168 | zf::Payload::Typed((data, serialiser)) => { 169 | if self.serialised_payload.is_empty() { 170 | (serialiser)(&mut self.serialised_payload, data.clone()).map_err(|e| { 171 | ZFError::from(anyhow!("Failed to serialise payload: {e:?}")) 172 | })?; 173 | } 174 | 175 | Ok(self.serialised_payload.as_slice()) 176 | } 177 | } 178 | } 179 | 180 | pub fn timestamp(&self) -> Timestamp { 181 | self.message.timestamp().into() 182 | } 183 | } 184 | 185 | struct ZFError(pub(crate) anyhow::Error); 186 | 187 | impl From for PyErr { 188 | fn from(value: ZFError) -> Self { 189 | let message_chain = value 190 | .0 191 | .chain() 192 | .map(|e| e.to_string()) 193 | .fold(String::default(), |acc, e| format!("{acc}\n{e}")); 194 | 195 | PyValueError::new_err(message_chain) 196 | } 197 | } 198 | 199 | impl From for ZFError { 200 | fn from(value: anyhow::Error) -> Self { 201 | Self(value) 202 | } 203 | } 204 | 205 | /// Converts the provided [Configuration] into a [PyObject]. 206 | /// 207 | /// This function is required because we cannot simply wrap a [Configuration] in a new type and then expose the same 208 | /// methods as [serde_json::Value]. 209 | pub fn configuration_into_py( 210 | py: Python, 211 | mut configuration: zf::Configuration, 212 | ) -> PyResult { 213 | match configuration.take() { 214 | Value::Array(arr) => { 215 | let py_list = PyList::empty(py); 216 | for v in arr { 217 | py_list.append(configuration_into_py(py, v.into())?)?; 218 | } 219 | Ok(py_list.to_object(py)) 220 | } 221 | Value::Object(obj) => { 222 | let py_dict = PyDict::new(py); 223 | for (k, v) in obj { 224 | py_dict.set_item(k, configuration_into_py(py, v.into())?)?; 225 | } 226 | Ok(py_dict.to_object(py)) 227 | } 228 | Value::Bool(b) => Ok(b.to_object(py)), 229 | Value::Number(n) => { 230 | if n.is_i64() { 231 | Ok(n.as_i64() 232 | .ok_or_else(|| { 233 | PyErr::from_value( 234 | PyTypeError::new_err(format!("Unable to convert {n:?} to i64")) 235 | .value(py), 236 | ) 237 | })? 238 | .to_object(py)) 239 | } else if n.is_u64() { 240 | Ok(n.as_u64() 241 | .ok_or_else(|| { 242 | PyErr::from_value( 243 | PyTypeError::new_err(format!("Unable to convert {n:?} to u64")) 244 | .value(py), 245 | ) 246 | })? 247 | .to_object(py)) 248 | } else { 249 | Ok(n.as_f64() 250 | .ok_or_else(|| { 251 | PyErr::from_value( 252 | PyTypeError::new_err(format!("Unable to convert {n:?} to f64")) 253 | .value(py), 254 | ) 255 | })? 256 | .to_object(py)) 257 | } 258 | } 259 | Value::String(s) => Ok(s.to_object(py)), 260 | Value::Null => Ok(py.None()), 261 | } 262 | } 263 | 264 | #[pyo3::pyclass] 265 | pub struct InputRaw(pub(crate) zf::InputRaw); 266 | 267 | impl From for InputRaw { 268 | fn from(input: zf::InputRaw) -> Self { 269 | Self(input) 270 | } 271 | } 272 | 273 | #[pyo3::pymethods] 274 | impl InputRaw { 275 | pub fn recv_async<'p>(&'p self, py: Python<'p>) -> PyResult<&'p PyAny> { 276 | let this = self.0.clone(); 277 | pyo3_asyncio::async_std::future_into_py(py, async move { 278 | match this.recv().await { 279 | Ok(link_message) => Ok(LinkMessage::from(link_message)), 280 | Err(e) => Err(ZFError::from(e).into()), 281 | } 282 | }) 283 | } 284 | 285 | pub fn try_recv(&self) -> PyResult> { 286 | match self.0.try_recv() { 287 | Ok(maybe_message) => Ok(maybe_message.map(LinkMessage::from)), 288 | Err(e) => Err(ZFError::from(e).into()), 289 | } 290 | } 291 | 292 | pub fn port_id(&self) -> &str { 293 | self.0.port_id() 294 | } 295 | } 296 | 297 | #[pyo3::pyclass] 298 | pub struct Inputs(pub(crate) zf::Inputs); 299 | 300 | impl From for Inputs { 301 | fn from(value: zf::Inputs) -> Self { 302 | Self(value) 303 | } 304 | } 305 | 306 | #[pyo3::pymethods] 307 | impl Inputs { 308 | pub fn take_raw(&mut self, port_id: &str) -> PyResult { 309 | self.0 310 | .take(port_id) 311 | .map(|input_builder| input_builder.raw().into()) 312 | .ok_or_else(|| { 313 | ZFError::from(anyhow!("Found no Input associated with port < {port_id} >")).into() 314 | }) 315 | } 316 | } 317 | 318 | #[pyo3::pyclass] 319 | pub struct OutputRaw(pub(crate) zf::OutputRaw); 320 | 321 | impl From for OutputRaw { 322 | fn from(output: zf::OutputRaw) -> Self { 323 | Self(output) 324 | } 325 | } 326 | 327 | #[pyo3::pymethods] 328 | impl OutputRaw { 329 | pub fn send_async<'p>( 330 | &'p self, 331 | py: Python<'p>, 332 | payload: Vec, 333 | timestamp: Option, 334 | ) -> PyResult<&'p PyAny> { 335 | let this = self.0.clone(); 336 | let port_id = self.0.port_id().clone(); 337 | pyo3_asyncio::async_std::future_into_py(py, async move { 338 | this.send(payload, timestamp).await.map_err(|e| { 339 | ZFError::from(anyhow!("Failed to send on < {port_id} >: {e:?}")).into() 340 | }) 341 | }) 342 | } 343 | 344 | pub fn try_send(&self, payload: &[u8], timestamp: Option) -> PyResult<()> { 345 | self.0.try_send(payload, timestamp).map_err(|e| { 346 | ZFError::from(anyhow!( 347 | "Call to `try_send` on < {} > failed with: {e:?}", 348 | self.0.port_id() 349 | )) 350 | .into() 351 | }) 352 | } 353 | 354 | pub fn port_id(&self) -> &str { 355 | self.0.port_id() 356 | } 357 | } 358 | 359 | #[pyo3::pyclass] 360 | pub struct Outputs(pub(crate) zf::Outputs); 361 | 362 | impl From for Outputs { 363 | fn from(outputs: zf::Outputs) -> Self { 364 | Self(outputs) 365 | } 366 | } 367 | 368 | #[pyo3::pymethods] 369 | impl Outputs { 370 | pub fn take_raw(&mut self, port_id: &str) -> PyResult { 371 | self.0 372 | .take(port_id) 373 | .map(|output_builder| output_builder.raw().into()) 374 | .ok_or_else(|| { 375 | ZFError::from(anyhow!( 376 | "Found not Output associated with port < {port_id} >" 377 | )) 378 | .into() 379 | }) 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /zenoh-flow-python/zenoh_flow_python/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright © 2022 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # ZettaScale Zenoh Team, 13 | # 14 | 15 | from .zenoh_flow_python import * 16 | 17 | from . import nodes as nodes 18 | from .nodes import Sink 19 | -------------------------------------------------------------------------------- /zenoh-flow-python/zenoh_flow_python/nodes.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright © 2022 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # ZettaScale Zenoh Team, 13 | # 14 | 15 | from zenoh_flow_python import Context, Inputs, Outputs 16 | from typing import Dict, Any 17 | from abc import ABC, abstractmethod 18 | 19 | 20 | class Operator(ABC): 21 | """ 22 | An `Operator` is a node performing transformation over the data it receives, 23 | outputting the end result to downstream node(s). 24 | 25 | An Operator hence possesses at least one Input and one Output. 26 | """ 27 | 28 | @abstractmethod 29 | def __init__( 30 | self, 31 | context: Context, 32 | configuration: Dict[str, Any], 33 | inputs: Inputs, 34 | outputs: Outputs, 35 | ): 36 | """ 37 | The constructor is called once by the Zenoh-Flow runtime when the data 38 | flow is loaded. 39 | """ 40 | 41 | @abstractmethod 42 | async def iteration(self) -> None: 43 | """ 44 | The `iteration` is called by the Zenoh-Flow runtime in a loop once the 45 | data flow is started. 46 | """ 47 | 48 | @abstractmethod 49 | def finalize(self) -> None: 50 | """ 51 | The `finalize` method is called by the Zenoh-Flow runtime before 52 | destroying the node (e.g., upon stopping the data flow graph). 53 | """ 54 | 55 | 56 | class Source(ABC): 57 | """ 58 | A `Source` feeds data into a data flow. 59 | 60 | As such, a Source only possesses Output(s). 61 | """ 62 | 63 | @abstractmethod 64 | def __init__( 65 | self, 66 | context: Context, 67 | configuration: Dict[str, Any], 68 | outputs: Outputs, 69 | ): 70 | """ 71 | The constructor is called once by the Zenoh-Flow runtime when the data 72 | flow is loaded. 73 | """ 74 | 75 | @abstractmethod 76 | async def iteration(self) -> None: 77 | """ 78 | The `iteration` is called by the Zenoh-Flow runtime in a loop once the 79 | data flow is started. 80 | """ 81 | 82 | @abstractmethod 83 | def finalize(self) -> None: 84 | """ 85 | The `finalize` method is called by the Zenoh-Flow runtime before 86 | destroying the node (e.g., upon stopping the data flow graph). 87 | """ 88 | 89 | 90 | class Sink(ABC): 91 | """ 92 | A `Sink` exposes the outcome of the data flow processing. 93 | 94 | As such, a Sink only possesses Input(s). 95 | """ 96 | 97 | @abstractmethod 98 | def __init__( 99 | self, 100 | context: Context, 101 | configuration: Dict[str, Any], 102 | inputs: Inputs, 103 | ): 104 | """ 105 | The constructor is called once by the Zenoh-Flow runtime when the data 106 | flow is loaded. 107 | """ 108 | 109 | @abstractmethod 110 | async def iteration(self) -> None: 111 | """ 112 | The `iteration` is called by the Zenoh-Flow runtime in a loop once the 113 | data flow is started. 114 | """ 115 | 116 | @abstractmethod 117 | def finalize(self) -> None: 118 | """ 119 | The `finalize` method is called by the Zenoh-Flow runtime before 120 | destroying the node (e.g., upon stopping the data flow graph). 121 | """ 122 | -------------------------------------------------------------------------------- /zenoh-flow-python/zenoh_flow_python/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eclipse-zenoh-flow/zenoh-flow-python/a9f930df6f7ca4f29308c966fe8d439db25085fe/zenoh-flow-python/zenoh_flow_python/py.typed -------------------------------------------------------------------------------- /zenoh-flow-python/zenoh_flow_python/zenoh_flow_python.pyi: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright © 2022 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # ZettaScale Zenoh Team, 13 | # 14 | 15 | from typing import final 16 | 17 | 18 | @final 19 | class InstanceId: 20 | """The unique identifier associated with a data flow instance.""" 21 | def __str__(self) -> str: ... 22 | 23 | 24 | @final 25 | class RuntimeId: 26 | """The unique identifier associated with a Zenoh-Flow Runtime.""" 27 | def __str__(self) -> str: ... 28 | 29 | 30 | @final 31 | class Context: 32 | """The execution context of a data flow instance.""" 33 | @property 34 | def data_flow_name(self) -> str: 35 | """ 36 | Returns the name of the data flow. 37 | 38 | All instances of the same data flow will share the same name. Their 39 | data flow instance id will however be unique to each. 40 | """ 41 | 42 | @property 43 | def data_flow_instance_id(self) -> InstanceId: 44 | """ 45 | Returns the unique identifier of this data flow instance. 46 | """ 47 | 48 | @property 49 | def runtime_id(self) -> RuntimeId: 50 | """ 51 | Returns the unique identifier of the Zenoh-Flow runtime managing the 52 | data flow instance. 53 | """ 54 | 55 | @property 56 | def library_path(self) -> pathlib.Path: 57 | """ 58 | Returns the path of the Python library currently running. 59 | 60 | This path will point to the Python code, not to the shared library 61 | wrapping it. 62 | """ 63 | 64 | @property 65 | def node_id(self) -> str: 66 | """Returns the node unique identifier in the data flow.""" 67 | 68 | 69 | @final 70 | class Timestamp: 71 | """ 72 | A timestamp made of a NTP64 and a Hybrid Logical Clock (HLC) unique 73 | identifier. 74 | """ 75 | 76 | @property 77 | def time(self) -> int: 78 | """Returns the number of milliseconds elapsed since UNIX EPOCH.""" 79 | 80 | @property 81 | def id(self) -> str: 82 | """ 83 | Returns the unique identifier of the HLC that generated the timestamp. 84 | 85 | The value is a hexadecimal representation. 86 | """ 87 | 88 | 89 | @final 90 | class LinkMessage: 91 | """ 92 | A message received from an InputRaw. 93 | 94 | A message is composed of two parts: a `payload` and a `Timestamp`. The 95 | payload is received as bytes and needs to be deserialised. 96 | """ 97 | 98 | @property 99 | def payload(self) -> bytes: 100 | """ 101 | Returns the payload, as bytes, associated with this message. 102 | """ 103 | 104 | @property 105 | def timestamp(self) -> Timestamp: 106 | """ 107 | Returns the Timestamp associated with this message. 108 | """ 109 | 110 | 111 | @final 112 | class InputRaw: 113 | """ 114 | A raw Zenoh-Flow Input, receiving serialised payload from upstream nodes. 115 | """ 116 | async def recv_async(self) -> LinkMessage: 117 | """ 118 | Retrieves, asynchronously, a message from the raw Input. 119 | 120 | # Exception 121 | 122 | An exception will be raised if the underlying channel is disconnected. A 123 | channel is disconnected only if the process holding the sending end is 124 | stopped (voluntarily or not). 125 | """ 126 | 127 | def try_recv(self) -> LinkMessage | None: 128 | """ 129 | Attempts to retrieve a message from the raw Input. 130 | 131 | If the channel is empty, this method will return None. 132 | 133 | # Exception 134 | 135 | An exception will be raised if the underlying channel is disconnected. A 136 | channel is disconnected only if the process holding the sending end is 137 | stopped (voluntarily or not). 138 | """ 139 | 140 | def port_id(self) -> str: 141 | """ 142 | Returns the port id associated with this raw Input. 143 | """ 144 | 145 | 146 | @final 147 | class Inputs: 148 | """ 149 | The channels *receiving* data from upstream nodes. 150 | """ 151 | def take_raw(self, port_id: str) -> InputRaw: 152 | """ 153 | Returns the raw Input associated with the provided port id. 154 | 155 | Note that the port id must be an exact match to what is written in the 156 | data flow descriptor. 157 | 158 | # Exception 159 | 160 | If no Input is associated with this port id, an exception is raised. 161 | """ 162 | 163 | 164 | @final 165 | class OutputRaw: 166 | """ 167 | A raw Zenoh-Flow Output, sending serialised payload to downstream nodes. 168 | """ 169 | async def send_async(self, payload: bytes, timestamp_ms: int | None) -> None: 170 | """ 171 | Sends, asynchronously, a payload on the raw Output. 172 | 173 | If a timestamp is provided, it will be interpreted as the number of 174 | milliseconds that elapsed since UNIX_EPOCH. 175 | 176 | If no timestamp is provided, a new timestamp will be generated by the 177 | Hybrid Logical Clock (HLC) used by the Zenoh-Flow runtime on which the 178 | node is executed. 179 | 180 | # Exception 181 | 182 | An exception will be raised if any of the underlying channels is 183 | disconnected. A channel is disconnected only if the process holding the 184 | receiving end is stopped (voluntarily or not). 185 | """ 186 | 187 | def try_send(self, payload: bytes, timestamp_ms: Timestamp | None) -> None: 188 | """ 189 | Attempts to send a payload on the raw Output. 190 | 191 | If a timestamp is provided, it will be interpreted as the number of 192 | milliseconds that elapsed since UNIX_EPOCH. 193 | 194 | If no timestamp is provided, a new timestamp will be generated by the 195 | Hybrid Logical Clock (HLC) used by the Zenoh-Flow runtime on which the 196 | node is executed. 197 | 198 | # Exceptions 199 | 200 | An exception will be raised if: 201 | 1. Any of the underlying channels is full, hence preventing sending the 202 | payload. 203 | 2. Any of the underlying channels is disconnected. A channel is 204 | disconnected only if the process holding the receiving end is stopped 205 | (voluntarily or not). 206 | """ 207 | 208 | def port_id(self) -> str: 209 | """ 210 | Returns the port id associated with this raw Output. 211 | """ 212 | 213 | 214 | @final 215 | class Outputs: 216 | """ 217 | The channels *sending* data to downstream nodes. 218 | """ 219 | def take_raw(self, port_id: str) -> OutputRaw: 220 | """ 221 | Returns the raw Output associated with the provided port id. 222 | 223 | Note that the port id must be an exact match to what is written in the 224 | data flow descriptor. 225 | 226 | # Exception 227 | 228 | If no Output is associated with this port id, an exception is raised. 229 | """ 230 | --------------------------------------------------------------------------------