diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8a4a82f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,122 @@ +name: Release + +on: + push: + branches: + - main + tags: + - 'v*' + workflow_dispatch: + +permissions: + contents: read + +jobs: + linux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-latest + target: x86_64 + - runner: ubuntu-latest + target: aarch64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-linux-${{ matrix.platform.target }} + path: dist + + musllinux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-latest + target: x86_64 + - runner: ubuntu-latest + target: aarch64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + manylinux: musllinux_1_2 + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-musllinux-${{ matrix.platform.target }} + path: dist + + macos: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: macos-12 + target: x86_64 + - runner: macos-14 + target: aarch64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-macos-${{ matrix.platform.target }} + path: dist + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: wheels-sdist + path: dist + + release: + name: Release + runs-on: ubuntu-latest + needs: [linux, musllinux, macos, sdist] + steps: + - uses: actions/download-artifact@v4 + - name: Publish to PyPI + uses: PyO3/maturin-action@v1 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + with: + command: upload + args: --non-interactive --skip-existing wheels-*/* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..486d322 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,35 @@ +name: Test + +on: + pull_request: + workflow_dispatch: + push: + branches: + - main + +permissions: + contents: read + +jobs: + lint-and-test: + name: "Lint and Test (Python ${{ matrix.python }})" + runs-on: ubuntu-latest + strategy: + matrix: + python: [ "3.12" ] + steps: + - uses: actions/checkout@v4 + - uses: extractions/setup-just@v2 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + - name: Build Rust module + uses: PyO3/maturin-action@v1 + with: + args: --out dist --interpreter python${{ matrix.python }} + sccache: 'true' + container: off + - run: pip install -r requirements.txt + - run: pip install dist/* + - name: Verify + run: just verify \ No newline at end of file diff --git a/.gitignore b/.gitignore index 82f9275..9ac98e0 100644 --- a/.gitignore +++ b/.gitignore @@ -85,7 +85,7 @@ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: -# .python-version +.python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. @@ -160,3 +160,9 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +# Rust +debug/ + +# IntelliJ +.idea \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..74adec8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,657 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "ambassador" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06baa18a48752d8177eca1bafa9970b2dc649a81b98d6dde9ae83bea1867030b" +dependencies = [ + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "block-buffer" +version = "0.11.0-pre.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded684142010808eb980d9974ef794da2bcf97d13396143b1515e9f0fb4a10e" +dependencies = [ + "crypto-common", +] + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-oid" +version = "0.10.0-pre.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e3352a27098ba6b09546e5f13b15165e6a88b5c2723afecb3ea9576b27e3ea" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.2.0-pre.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7aa2ec04f5120b830272a481e8d9d8ba4dda140d2cda59b0f1110d5eb93c38e" +dependencies = [ + "getrandom", + "hybrid-array", + "rand_core", +] + +[[package]] +name = "digest" +version = "0.11.0-pre.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065d93ead7c220b85d5b4be4795d8398eac4ff68b5ee63895de0a3c1fb6edf25" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hybrid-array" +version = "0.2.0-rc.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d306b679262030ad8813a82d4915fc04efff97776e4db7f8eb5137039d56400" +dependencies = [ + "typenum", +] + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "pyo3" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1962a33ed2a201c637fc14a4e0fd4e06e6edfdeee6a5fede0dab55507ad74cf7" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab7164b2202753bd33afc7f90a10355a719aa973d1f94502c50d06f3488bc420" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6424906ca49013c0829c5c1ed405e20e2da2dc78b82d198564880a704e6a7b7" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b2f19e153122d64afd8ce7aaa72f06a00f52e34e1d1e74b6d71baea396460a" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd698c04cac17cf0fe63d47790ab311b8b25542f5cb976b65c374035c50f1eef" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "restate-sdk-python-core" +version = "0.1.0" +dependencies = [ + "pyo3", + "restate-sdk-shared-core", + "tracing-subscriber", +] + +[[package]] +name = "restate-sdk-shared-core" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dabf845f539793d77d2530e9e011048e6eda089ceb5d99b5d950c7bd216c2765" +dependencies = [ + "ambassador", + "base64", + "bytes", + "bytes-utils", + "paste", + "prost", + "sha2", + "strum", + "thiserror", + "tracing", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "sha2" +version = "0.11.0-pre.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f33549bf3064b62478926aa89cbfc7c109aab66ae8f0d5d2ef839e482cc30d6" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.68", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..157004c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "restate-sdk-python-core" +version = "0.1.0" +edition = "2021" + +[package.metadata.maturin] +name = "restate_sdk._internal" + +[lib] +name = "restate_sdk_python_core" +crate-type = ["cdylib"] +doc = false + +[dependencies] +pyo3 = { version = "0.22.0", features = ["extension-module"] } +tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] } +restate-sdk-shared-core = { version = "0.0.3" } diff --git a/Justfile b/Justfile index d520135..dce6d6f 100644 --- a/Justfile +++ b/Justfile @@ -2,19 +2,26 @@ python := "python3" +default: + @echo "Available recipes:" + @echo " mypy - Run mypy for type checking" + @echo " pylint - Run pylint for linting" + @echo " test - Run pytest for testing" + @echo " verify - Run mypy, pylint, test" + # Recipe to run mypy for type checking mypy: @echo "Running mypy..." - {{python}} -m mypy --check-untyped-defs --ignore-missing-imports src/ + {{python}} -m mypy --check-untyped-defs --ignore-missing-imports python/restate/ # Recipe to run pylint for linting pylint: @echo "Running pylint..." - {{python}} -m pylint src/ + {{python}} -m pylint python/restate test: - @echo "Running tests..." - {{python}} -m unittest discover tests/ + @echo "Running Python tests..." + {{python}} -m pytest tests/* # Recipe to run both mypy and pylint verify: mypy pylint test @@ -23,18 +30,18 @@ verify: mypy pylint test # Recipe to build the project build: @echo "Building the project..." - {{python}} setup.py sdist bdist_wheel + maturin build --release clean: @echo "Cleaning the project" - rm -rf dist/ + cargo clean example: #!/usr/bin/env bash if [ -z "$PYTHONPATH" ]; then - export PYTHONPATH="src/" + export PYTHONPATH="examples/" else - export PYTHONPATH="$PYTHONPATH:src/" + export PYTHONPATH="$PYTHONPATH:examples/" fi hypercorn --config hypercorn-config.toml example:app @@ -42,10 +49,3 @@ docker: @echo "Creating dockerized example example:main" docker build . -t example:main - -# Default recipe to show help message -default: - @echo "Available recipes:" - @echo " mypy - Run mypy for type checking" - @echo " pylint - Run pylint for linting" - @echo " verify - Run both mypy and pylint" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f08ba1f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Restate + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 3d6d42a..47ca8cd 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,92 @@ -## Python SDK for restate +[![Documentation](https://img.shields.io/badge/doc-reference-blue)](https://docs.restate.dev) +[![Examples](https://img.shields.io/badge/view-examples-blue)](https://github.com/restatedev/examples) +[![Discord](https://img.shields.io/discord/1128210118216007792?logo=discord)](https://discord.gg/skW3AZ6uGd) +[![Twitter](https://img.shields.io/twitter/follow/restatedev.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=restatedev) +# Python SDK for restate -### Prerequisites -* python 3 -* pip -* just +> [!WARNING] +> The Python SDK is currently in active development, and might break across releases. + +[Restate](https://restate.dev/) is a system for easily building resilient applications using *distributed durable async/await*. This repository contains the Restate SDK for writing services in **Python**. + +## Community + +* πŸ€—οΈ [Join our online community](https://discord.gg/skW3AZ6uGd) for help, sharing feedback and talking to the community. +* πŸ“– [Check out our documentation](https://docs.restate.dev) to get quickly started! +* πŸ“£ [Follow us on Twitter](https://twitter.com/restatedev) for staying up to date. +* πŸ™‹ [Create a GitHub issue](https://github.com/restatedev/sdk-typescript/issues) for requesting a new feature or reporting a problem. +* 🏠 [Visit our GitHub org](https://github.com/restatedev) for exploring other repositories. + +## Using the SDK + +To use this SDK, add the dependency to your project: + +```shell +pip install restate_sdk +``` + +## Versions + +The Python SDK is currently in active development, and might break across releases. + +The compatibility with Restate is described in the following table: + +| Restate Server\sdk-typescript | 0.0/0.1 | +|-------------------------------|---------| +| 1.0 | βœ… | + +## Contributing + +We’re excited if you join the Restate community and start contributing! +Whether it is feature requests, bug reports, ideas & feedback or PRs, we appreciate any and all contributions. +We know that your time is precious and, therefore, deeply value any effort to contribute! ### Local development -* pip install -r requirements.txt -* just build +* Python 3 +* PyEnv or VirtualEnv +* [just](https://github.com/casey/just) +* [Rust toolchain](https://rustup.rs/) + +Setup your virtual environment using the tool of your choice, e.g. VirtualEnv: + +```shell +python3 -m venv .venv +source venv/bin/activate +``` + +Install `maturin`: + +```shell +pip install maturin +``` + +Now build the Rust module and include opt-in additional dev dependencies: + +```shell +maturin dev -E test,lint +``` + +You usually need to build the Rust module only once, but you might need to rebuild it on pulls. + +For linting and testing: + +```shell +just verify +``` + +## Releasing the package + +Pull latest main: +```shell +git checkout main && git pull +``` -### Justfile commands -* just clean -* just verify -* just build -* just example +Update module version in `Cargo.toml`, commit it. Then push tag, e.g.: +``` +git tag -m "Release v0.1.0" v0.1.0 +git push origin v0.1.0 +``` \ No newline at end of file diff --git a/src/example.py b/examples/example.py similarity index 100% rename from src/example.py rename to examples/example.py diff --git a/project.toml b/project.toml deleted file mode 100644 index 813826f..0000000 --- a/project.toml +++ /dev/null @@ -1,9 +0,0 @@ -[project] -name = "restate-sdk-python" -version = "0.0.1" -description = "A Python SDK for Restate" -authors = ["Restate developers "] - -[build-system] -requires = ["setuptools", "wheel"] -build-backend = "setuptools.build_meta" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..23f650f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[project] +name = "restate-sdk" +description = "A Python SDK for Restate" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Software Development :: Libraries :: Application Frameworks" +] +dynamic = ["version"] +license = { file = "LICENSE" } +authors = [ + { name = "Restate Developers", email = "dev@restate.dev" } +] + +[project.optional-dependencies] +test = ["pytest", "hypercorn"] +lint = ["mypy", "pylint"] + +[build-system] +requires = ["maturin>=1.6,<2.0"] +build-backend = "maturin" + +[tool.maturin] +features = ["pyo3/extension-module"] +module-name = "restate._internal" +python-source = "python" \ No newline at end of file diff --git a/src/restate/__init__.py b/python/restate/__init__.py similarity index 100% rename from src/restate/__init__.py rename to python/restate/__init__.py diff --git a/src/restate/context.py b/python/restate/context.py similarity index 100% rename from src/restate/context.py rename to python/restate/context.py diff --git a/src/restate/discovery.py b/python/restate/discovery.py similarity index 100% rename from src/restate/discovery.py rename to python/restate/discovery.py diff --git a/src/restate/endpoint.py b/python/restate/endpoint.py similarity index 100% rename from src/restate/endpoint.py rename to python/restate/endpoint.py diff --git a/src/restate/exceptions.py b/python/restate/exceptions.py similarity index 100% rename from src/restate/exceptions.py rename to python/restate/exceptions.py diff --git a/src/restate/handler.py b/python/restate/handler.py similarity index 100% rename from src/restate/handler.py rename to python/restate/handler.py diff --git a/src/restate/object.py b/python/restate/object.py similarity index 100% rename from src/restate/object.py rename to python/restate/object.py diff --git a/src/restate/serde.py b/python/restate/serde.py similarity index 100% rename from src/restate/serde.py rename to python/restate/serde.py diff --git a/src/restate/server.py b/python/restate/server.py similarity index 100% rename from src/restate/server.py rename to python/restate/server.py diff --git a/src/restate/server_context.py b/python/restate/server_context.py similarity index 100% rename from src/restate/server_context.py rename to python/restate/server_context.py diff --git a/src/restate/server_types.py b/python/restate/server_types.py similarity index 100% rename from src/restate/server_types.py rename to python/restate/server_types.py diff --git a/src/restate/service.py b/python/restate/service.py similarity index 100% rename from src/restate/service.py rename to python/restate/service.py diff --git a/src/restate/vm.py b/python/restate/vm.py similarity index 90% rename from src/restate/vm.py rename to python/restate/vm.py index 4b99e1c..f3b273a 100644 --- a/src/restate/vm.py +++ b/python/restate/vm.py @@ -9,13 +9,13 @@ # https://github.com/restatedev/sdk-typescript/blob/main/LICENSE # """ -wrap the restate_sdk_python_core.PyVM class +wrap the restate._internal.PyVM class """ # pylint: disable=E1101 from dataclasses import dataclass import typing -import restate_sdk_python_core # pylint: disable=import-error +from restate._internal import PyVM, PyFailure, PySuspended, PyVoid # pylint: disable=import-error,no-name-in-module @dataclass class Invocation: @@ -58,12 +58,12 @@ def __init__(self, *args: object) -> None: # pylint: disable=too-many-public-methods class VMWrapper: """ - A wrapper class for the restate_sdk_python_core.PyVM class. - It provides a type-friendly interface to our shared vm. + A wrapper class for the restate_sdk._internal.PyVM class. + It provides a type-friendly interface to our shared vm. """ def __init__(self, headers: typing.List[typing.Tuple[str, str]]): - self.vm = restate_sdk_python_core.PyVM(headers) + self.vm = PyVM(headers) def get_response_head(self) -> typing.Tuple[int, typing.Iterable[typing.Tuple[str, str]]]: """ @@ -104,18 +104,18 @@ def take_async_result(self, handle: typing.Any) -> AsyncResultType: result = self.vm.take_async_result(handle) if result is None: return NOT_READY - if isinstance(result, restate_sdk_python_core.PyVoid): + if isinstance(result, PyVoid): # success with an empty value return None if isinstance(result, bytes): # success with a non empty value return result - if isinstance(result, restate_sdk_python_core.PyFailure): + if isinstance(result, PyFailure): # a terminal failure code = result.code message = result.message return Failure(code, message) - if isinstance(result, restate_sdk_python_core.PySuspended): + if isinstance(result, PySuspended): # the state machine had suspended raise SUSPENDED raise ValueError(f"Unknown result type: {result}") @@ -163,7 +163,7 @@ def sys_write_output_failure(self, output: Failure): Returns: None """ - res = restate_sdk_python_core.PyFailure(output.code, output.message) + res = PyFailure(output.code, output.message) self.vm.sys_write_output_failure(res) @@ -230,7 +230,7 @@ def sys_run_enter(self, name: str) -> typing.Union[bytes, None, Failure]: result = self.vm.sys_run_enter(name) if result is None: return None - if isinstance(result, restate_sdk_python_core.PyFailure): + if isinstance(result, PyFailure): return Failure(result.code, result.message) # pylint: disable=protected-access assert isinstance(result, bytes) return result @@ -251,7 +251,7 @@ def sys_reject_awakeable(self, name: str, failure: Failure): """ Reject """ - py_failure = restate_sdk_python_core.PyFailure(failure.code, failure.message) + py_failure = PyFailure(failure.code, failure.message) self.vm.sys_complete_awakeable_failure(name, py_failure) def sys_run_exit_success(self, output: bytes) -> int: @@ -280,7 +280,7 @@ def sys_complete_promise_success(self, name: str, value: bytes) -> int: def sys_complete_promise_failure(self, name: str, failure: Failure) -> int: """reject the promise on failure""" - res = restate_sdk_python_core.PyFailure(failure.code, failure.message) + res = PyFailure(failure.code, failure.message) return self.vm.sys_complete_promise_failure(name, res) def sys_run_exit_failure(self, output: Failure) -> int: @@ -291,7 +291,7 @@ def sys_run_exit_failure(self, output: Failure) -> int: name: The name of the side effect. output: The output of the side effect. """ - res = restate_sdk_python_core.PyFailure(output.code, output.message) + res = PyFailure(output.code, output.message) return self.vm.sys_run_exit_failure(res) def sys_end(self): diff --git a/src/restate/workflow.py b/python/restate/workflow.py similarity index 100% rename from src/restate/workflow.py rename to python/restate/workflow.py diff --git a/requirements.txt b/requirements.txt index d98f4e0..1f274b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ mypy pylint -wheel hypercorn +maturin +pytest \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 9ed54cd..0000000 --- a/setup.py +++ /dev/null @@ -1,11 +0,0 @@ -from setuptools import setup, find_packages - -setup( - name='restate-sdk-python', - version='0.1.0', - packages=find_packages(where='src'), - package_dir={'': 'src'}, - install_requires=[ - 'starlette' - ], -) diff --git a/shell.nix b/shell.nix index b3bf1da..1460cb7 100755 --- a/shell.nix +++ b/shell.nix @@ -6,7 +6,7 @@ python3 python3Packages.pip python3Packages.virtualenv - just + just ]); runScript = '' bash diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..58bf863 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,498 @@ +use pyo3::create_exception; +use pyo3::prelude::*; +use pyo3::types::{PyBytes, PyNone}; +use restate_sdk_shared_core::{ + AsyncResultHandle, CoreVM, Failure, Header, Input, NonEmptyValue, ResponseHead, RunEnterResult, + SuspendedOrVMError, TakeOutputResult, Target, VMError, Value, VM, +}; +use std::borrow::Cow; +use std::time::Duration; + +// Data model + +#[pyclass] +#[derive(Clone)] +struct PyHeader { + #[pyo3(get, set)] + key: String, + #[pyo3(get, set)] + value: String, +} + +impl From
for PyHeader { + fn from(h: Header) -> Self { + PyHeader { + key: h.key.into(), + value: h.value.into(), + } + } +} + +#[pyclass] +struct PyResponseHead { + #[pyo3(get, set)] + status_code: u16, + #[pyo3(get, set)] + headers: Vec<(String, String)>, +} + +impl From for PyResponseHead { + fn from(value: ResponseHead) -> Self { + PyResponseHead { + status_code: value.status_code, + headers: value + .headers + .into_iter() + .map(|Header { key, value }| (key.into(), value.into())) + .collect(), + } + } +} + +fn take_output_result_into_py( + py: Python, + take_output_result: TakeOutputResult, +) -> Bound<'_, PyAny> { + match take_output_result { + TakeOutputResult::Buffer(b) => PyBytes::new_bound(py, &b).into_any(), + TakeOutputResult::EOF => PyNone::get_bound(py).to_owned().into_any(), + } +} + +type PyAsyncResultHandle = u32; + +#[pyclass] +struct PyVoid; + +#[pyclass] +struct PySuspended; + +#[pyclass] +#[derive(Clone)] +struct PyFailure { + #[pyo3(get, set)] + code: u16, + #[pyo3(get, set)] + message: String, +} + +#[pymethods] +impl PyFailure { + #[new] + fn new(code: u16, message: String) -> PyFailure { + Self { code, message } + } +} + +impl From for PyFailure { + fn from(value: Failure) -> Self { + PyFailure { + code: value.code, + message: value.message, + } + } +} + +impl From for Failure { + fn from(value: PyFailure) -> Self { + Failure { + code: value.code, + message: value.message, + } + } +} + +#[pyclass] +pub struct PyInput { + #[pyo3(get, set)] + invocation_id: String, + #[pyo3(get, set)] + random_seed: u64, + #[pyo3(get, set)] + key: String, + #[pyo3(get, set)] + headers: Vec, + #[pyo3(get, set)] + input: Vec, +} + +impl From for PyInput { + fn from(value: Input) -> Self { + PyInput { + invocation_id: value.invocation_id, + random_seed: value.random_seed, + key: value.key, + headers: value.headers.into_iter().map(Into::into).collect(), + input: value.input, + } + } +} + +// Errors and Exceptions + +#[derive(Debug)] +struct PyVMError(VMError); + +// Python representation of VMError +create_exception!( + restate_sdk_python_core, + VMException, + pyo3::exceptions::PyException, + "Restate VM exception." +); + +impl From for PyErr { + fn from(value: PyVMError) -> Self { + VMException::new_err(value.0.to_string()) + } +} + +impl From for PyVMError { + fn from(value: VMError) -> Self { + PyVMError(value) + } +} + +// VM implementation + +#[pyclass] +struct PyVM { + vm: CoreVM, +} + +#[pymethods] +impl PyVM { + #[new] + fn new(headers: Vec<(String, String)>) -> Result { + Ok(Self { + vm: CoreVM::new(headers)?, + }) + } + + fn get_response_head(self_: PyRef<'_, Self>) -> PyResponseHead { + self_.vm.get_response_head().into() + } + + // Notifications + + fn notify_input(mut self_: PyRefMut<'_, Self>, buffer: &Bound<'_, PyBytes>) { + self_.vm.notify_input(buffer.as_bytes().to_vec()); + } + + fn notify_input_closed(mut self_: PyRefMut<'_, Self>) { + self_.vm.notify_input_closed(); + } + + #[pyo3(signature = (error, description=None))] + fn notify_error(mut self_: PyRefMut<'_, Self>, error: String, description: Option) { + self_.vm.notify_error( + Cow::Owned(error), + description.map(Cow::Owned).unwrap_or(Cow::Borrowed("")), + ); + } + + // Take(s) + + /// Returns either bytes or None, indicating EOF + fn take_output(mut self_: PyRefMut<'_, Self>) -> Bound<'_, PyAny> { + take_output_result_into_py(self_.py(), self_.vm.take_output()) + } + + fn is_ready_to_execute(self_: PyRef<'_, Self>) -> Result { + self_.vm.is_ready_to_execute().map_err(Into::into) + } + + fn notify_await_point(mut self_: PyRefMut<'_, Self>, handle: PyAsyncResultHandle) { + self_.vm.notify_await_point(handle.into()) + } + + /// Returns either: + /// + /// * `PyBytes` in case the async result holds success value + /// * `PyFailure` in case the async result holds failure value + /// * `PyVoid` in case the async result holds Void value + /// * `PySuspended` in case the state machine is suspended + /// * `None` in case the async result is not yet present + fn take_async_result( + mut self_: PyRefMut<'_, Self>, + handle: PyAsyncResultHandle, + ) -> Result, PyVMError> { + let res = self_.vm.take_async_result(AsyncResultHandle::from(handle)); + + let py = self_.py(); + + match res { + Err(SuspendedOrVMError::VM(e)) => Err(e.into()), + Err(SuspendedOrVMError::Suspended(_)) => { + Ok(PySuspended.into_py(py).into_bound(py).into_any()) + } + Ok(None) => Ok(PyNone::get_bound(py).to_owned().into_any()), + Ok(Some(Value::Void)) => Ok(PyVoid.into_py(py).into_bound(py).into_any()), + Ok(Some(Value::Success(b))) => Ok(PyBytes::new_bound(py, &b).into_any()), + Ok(Some(Value::Failure(f))) => { + Ok(PyFailure::from(f).into_py(py).into_bound(py).into_any()) + } + } + } + + // Syscall(s) + + fn sys_input(mut self_: PyRefMut<'_, Self>) -> Result { + self_.vm.sys_input().map(Into::into).map_err(Into::into) + } + + fn sys_get_state( + mut self_: PyRefMut<'_, Self>, + key: String, + ) -> Result { + self_ + .vm + .sys_get_state(key) + .map(Into::into) + .map_err(Into::into) + } + + fn sys_set_state( + mut self_: PyRefMut<'_, Self>, + key: String, + buffer: &Bound<'_, PyBytes>, + ) -> Result<(), PyVMError> { + self_ + .vm + .sys_set_state(key, buffer.as_bytes().to_vec()) + .map_err(Into::into) + } + + fn sys_clear_state(mut self_: PyRefMut<'_, Self>, key: String) -> Result<(), PyVMError> { + self_.vm.sys_clear_state(key).map_err(Into::into) + } + + fn sys_clear_all_state(mut self_: PyRefMut<'_, Self>) -> Result<(), PyVMError> { + self_.vm.sys_clear_all_state().map_err(Into::into) + } + + fn sys_sleep( + mut self_: PyRefMut<'_, Self>, + millis: u64, + ) -> Result { + self_ + .vm + .sys_sleep(Duration::from_millis(millis)) + .map(Into::into) + .map_err(Into::into) + } + + #[pyo3(signature = (service, handler, buffer, key=None))] + fn sys_call( + mut self_: PyRefMut<'_, Self>, + service: String, + handler: String, + buffer: &Bound<'_, PyBytes>, + key: Option, + ) -> Result { + self_ + .vm + .sys_call( + Target { + service, + handler, + key, + }, + buffer.as_bytes().to_vec(), + ) + .map(Into::into) + .map_err(Into::into) + } + + #[pyo3(signature = (service, handler, buffer, key=None, delay=None))] + fn sys_send( + mut self_: PyRefMut<'_, Self>, + service: String, + handler: String, + buffer: &Bound<'_, PyBytes>, + key: Option, + delay: Option, + ) -> Result<(), PyVMError> { + self_ + .vm + .sys_send( + Target { + service, + handler, + key, + }, + buffer.as_bytes().to_vec(), + delay.map(Duration::from_millis), + ) + .map_err(Into::into) + } + + fn sys_awakeable( + mut self_: PyRefMut<'_, Self>, + ) -> Result<(String, PyAsyncResultHandle), PyVMError> { + self_ + .vm + .sys_awakeable() + .map(|(id, handle)| (id, handle.into())) + .map_err(Into::into) + } + + fn sys_complete_awakeable_success( + mut self_: PyRefMut<'_, Self>, + id: String, + buffer: &Bound<'_, PyBytes>, + ) -> Result<(), PyVMError> { + self_ + .vm + .sys_complete_awakeable(id, NonEmptyValue::Success(buffer.as_bytes().to_vec())) + .map_err(Into::into) + } + + fn sys_complete_awakeable_failure( + mut self_: PyRefMut<'_, Self>, + id: String, + value: PyFailure, + ) -> Result<(), PyVMError> { + self_ + .vm + .sys_complete_awakeable(id, NonEmptyValue::Failure(value.into())) + .map_err(Into::into) + } + + fn sys_get_promise( + mut self_: PyRefMut<'_, Self>, + key: String, + ) -> Result { + self_ + .vm + .sys_get_promise(key) + .map(Into::into) + .map_err(Into::into) + } + + fn sys_peek_promise( + mut self_: PyRefMut<'_, Self>, + key: String, + ) -> Result { + self_ + .vm + .sys_peek_promise(key) + .map(Into::into) + .map_err(Into::into) + } + + fn sys_complete_promise_success( + mut self_: PyRefMut<'_, Self>, + key: String, + buffer: &Bound<'_, PyBytes>, + ) -> Result { + self_ + .vm + .sys_complete_promise(key, NonEmptyValue::Success(buffer.as_bytes().to_vec())) + .map(Into::into) + .map_err(Into::into) + } + + fn sys_complete_promise_failure( + mut self_: PyRefMut<'_, Self>, + key: String, + value: PyFailure, + ) -> Result { + self_ + .vm + .sys_complete_promise(key, NonEmptyValue::Failure(value.into())) + .map(Into::into) + .map_err(Into::into) + } + + /// Returns either: + /// + /// * `PyBytes`, in case the run was executed with success + /// * `PyFailure`, in case the run was executed with failure + /// * `None` in case the run was not executed + fn sys_run_enter( + mut self_: PyRefMut<'_, Self>, + name: String, + ) -> Result, PyVMError> { + let result = self_.vm.sys_run_enter(name)?; + + let py = self_.py(); + + Ok(match result { + RunEnterResult::Executed(NonEmptyValue::Success(b)) => { + PyBytes::new_bound(py, &b).into_any() + } + RunEnterResult::Executed(NonEmptyValue::Failure(f)) => { + PyFailure::from(f).into_py(py).into_bound(py).into_any() + } + RunEnterResult::NotExecuted => PyNone::get_bound(py).to_owned().into_any(), + }) + } + + fn sys_run_exit_success( + mut self_: PyRefMut<'_, Self>, + buffer: &Bound<'_, PyBytes>, + ) -> Result { + self_ + .vm + .sys_run_exit(NonEmptyValue::Success(buffer.as_bytes().to_vec())) + .map(Into::into) + .map_err(Into::into) + } + + fn sys_run_exit_failure( + mut self_: PyRefMut<'_, Self>, + value: PyFailure, + ) -> Result { + self_ + .vm + .sys_run_exit(NonEmptyValue::Failure(value.into())) + .map(Into::into) + .map_err(Into::into) + } + + fn sys_write_output_success( + mut self_: PyRefMut<'_, Self>, + buffer: &Bound<'_, PyBytes>, + ) -> Result<(), PyVMError> { + self_ + .vm + .sys_write_output(NonEmptyValue::Success(buffer.as_bytes().to_vec())) + .map(Into::into) + .map_err(Into::into) + } + + fn sys_write_output_failure( + mut self_: PyRefMut<'_, Self>, + value: PyFailure, + ) -> Result<(), PyVMError> { + self_ + .vm + .sys_write_output(NonEmptyValue::Failure(value.into())) + .map(Into::into) + .map_err(Into::into) + } + + fn sys_end(mut self_: PyRefMut<'_, Self>) -> Result<(), PyVMError> { + self_.vm.sys_end().map(Into::into).map_err(Into::into) + } +} + +#[pymodule] +fn _internal(m: &Bound<'_, PyModule>) -> PyResult<()> { + use tracing_subscriber::EnvFilter; + + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_env("RESTATE_CORE_LOG")) + .init(); + + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add("VMException", m.py().get_type_bound::())?; + Ok(()) +} diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 7a38cf2..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# -# Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH -# -# This file is part of the Restate SDK for Node.js/TypeScript, -# which is released under the MIT license. -# -# You can find a copy of the license in file LICENSE in the root -# directory of this repository or package, or at -# https://github.com/restatedev/sdk-typescript/blob/main/LICENSE -# -""" -This module defines the Service class for representing a restate service. -""" \ No newline at end of file diff --git a/tests/serde.py b/tests/serde.py new file mode 100644 index 0000000..71c8fef --- /dev/null +++ b/tests/serde.py @@ -0,0 +1,5 @@ +from restate.serde import BytesSerde + +def test_bytes_serde(): + s = BytesSerde() + assert bytes(range(20)) == s.serialize(bytes(range(20))) \ No newline at end of file diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 22057dd..0000000 --- a/tox.ini +++ /dev/null @@ -1,20 +0,0 @@ -[tox] -envlist = py37, py38, py39, lint, mypy - -[testenv] -deps = - pytest - mypy - pylint -commands = - pytest - mypy src/ - pylint src/ - -[testenv:lint] -deps = pylint -commands = pylint src/ - -[testenv:mypy] -deps = mypy -commands = mypy src/