├── .flake8 ├── .github └── workflows │ ├── docs.yml │ └── wheels.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── MANIFEST.in ├── Pipfile ├── README.md ├── _custom_build └── backend.py ├── benchmark ├── addressbook.capnp ├── addressbook.capnp.orphan.py ├── addressbook.capnp.py ├── addressbook.proto ├── addressbook.proto.py ├── addressbook_pb2.py ├── bin │ ├── README.md │ ├── pycapnp-carsales │ ├── pycapnp-catrank │ ├── pycapnp-eval │ ├── pyproto-carsales │ ├── pyproto-catrank │ ├── pyproto-eval │ ├── pyproto_cpp-carsales │ ├── pyproto_cpp-catrank │ ├── pyproto_cpp-eval │ ├── requirements.txt │ ├── run_all.py │ └── runner.py ├── carsales.capnp ├── carsales.proto ├── carsales_pb2.py ├── carsales_proto.py ├── carsales_pycapnp.py ├── catrank.capnp ├── catrank.proto ├── catrank_pb2.py ├── catrank_proto.py ├── catrank_pycapnp.py ├── common.py ├── common_fast.pyx ├── eval.capnp ├── eval.proto ├── eval_pb2.py ├── eval_proto.py └── eval_pycapnp.py ├── buildutils ├── __init__.py ├── build.py └── bundle.py ├── capnp ├── __init__.pxd ├── __init__.py ├── _gen.py ├── helpers │ ├── __init__.pxd │ ├── capabilityHelper.cpp │ ├── capabilityHelper.h │ ├── checkCompiler.h │ ├── deserialize.h │ ├── fixMaybe.h │ ├── helpers.pxd │ ├── non_circular.pxd │ ├── rpcHelper.h │ └── serialize.h ├── includes │ ├── __init__.pxd │ ├── capnp_cpp.pxd │ ├── schema_cpp.pxd │ └── types.pxd ├── lib │ ├── __init__.pxd │ ├── __init__.py │ ├── capnp.pxd │ ├── capnp.pyx │ └── pickle_helper.py └── templates │ ├── module.pyx │ └── setup.py.tmpl ├── docs ├── Makefile ├── _templates │ └── versioning.html ├── capnp.rst ├── conf.py ├── index.rst ├── install.rst └── quickstart.rst ├── examples ├── addressbook.capnp ├── addressbook.py ├── async_calculator_client.py ├── async_calculator_server.py ├── async_client.py ├── async_reconnecting_ssl_client.py ├── async_server.py ├── async_socket_message_client.py ├── async_socket_message_server.py ├── async_ssl_calculator_client.py ├── async_ssl_calculator_server.py ├── async_ssl_client.py ├── async_ssl_server.py ├── calculator.capnp ├── selfsigned.cert ├── selfsigned.key └── thread.capnp ├── pyproject.toml ├── requirements.txt ├── scripts ├── capnp-json.py └── capnp_test_pycapnp.py ├── setup.cfg ├── setup.py ├── test ├── addressbook with spaces.capnp ├── addressbook-with-dashes.capnp ├── addressbook.capnp ├── all-types.binary ├── all-types.packed ├── all-types.txt ├── all_types.capnp ├── annotations.capnp ├── bar.capnp ├── foo.capnp ├── schemas │ ├── child.capnp │ └── parent.capnp ├── test_capability.capnp ├── test_capability.py ├── test_capability_context.py ├── test_context_manager.py ├── test_examples.py ├── test_large_read.capnp ├── test_large_read.py ├── test_load.py ├── test_memory_handling.py ├── test_object.py ├── test_regression.py ├── test_response.capnp ├── test_response.py ├── test_rpc.py ├── test_rpc_calculator.py ├── test_schema.py ├── test_serialization.py └── test_struct.py └── tox.ini /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | extend-ignore = E203,E211,E225,E226,E227,E231,E251,E261,E262,E265,E402,E999 4 | max-complexity = 10 5 | per-file-ignores = 6 | test/test_examples.py: C901 7 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | docs: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - name: Set up Python 3.8 11 | uses: actions/setup-python@v4 12 | with: 13 | python-version: 3.8 14 | - name: Install dependencies 15 | run: | 16 | python -m pip install --upgrade pip 17 | pip install -r requirements.txt 18 | - name: Build pycapnp and install 19 | run: | 20 | python setup.py build 21 | pip install . 22 | - name: Build documentation 23 | run: | 24 | sphinx-build docs build/html 25 | -------------------------------------------------------------------------------- /.github/workflows/wheels.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build_wheels: 7 | name: Build wheels on ${{ matrix.os }} ${{ matrix.arch }} 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | max-parallel: 99 11 | fail-fast: false 12 | matrix: 13 | include: 14 | - os: ubuntu-latest 15 | arch: x86_64 16 | - os: ubuntu-latest 17 | arch: i686 18 | - os: ubuntu-latest 19 | arch: aarch64 20 | - os: ubuntu-latest 21 | arch: ppc64le 22 | - os: ubuntu-latest 23 | arch: s390x 24 | 25 | - os: macOS-latest 26 | arch: x86_64 27 | - os: macOS-latest 28 | arch: arm64 29 | # Disabled until someone figures out how to build capnproto for arm64 and x86_64 simultaneously 30 | # - os: macOS-latest 31 | # arch: universal2 32 | 33 | - os: windows-2019 34 | arch: AMD64 35 | - os: windows-2019 36 | arch: x86 37 | # Does not build currently 38 | # - os: windows-2019 39 | # arch: ARM64 40 | 41 | steps: 42 | - uses: actions/checkout@v4 43 | 44 | - name: Set up QEMU 45 | if: runner.os == 'Linux' 46 | uses: docker/setup-qemu-action@v2 47 | 48 | - name: Build wheels 49 | uses: pypa/cibuildwheel@v2.23.3 50 | with: 51 | output-dir: wheelhouse 52 | env: 53 | CIBW_ARCHS: ${{ matrix.arch }} 54 | # TODO: Disable building PyPy wheels. If the build system gets modernized, this should be 55 | # auto-detected based on the Cython dependency. 56 | CIBW_SKIP: pp* 57 | CIBW_TEST_REQUIRES: pytest pytest-asyncio 58 | CIBW_TEST_COMMAND: pytest {project} 59 | # Only needed to make the macosx arm64 build work 60 | CMAKE_OSX_ARCHITECTURES: "${{ matrix.arch == 'arm64' && 'arm64' || '' }}" 61 | 62 | - uses: actions/upload-artifact@v4 63 | with: 64 | name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} 65 | path: ./wheelhouse/*.whl 66 | 67 | build_sdist: 68 | name: Build source distribution 69 | runs-on: ubuntu-latest 70 | steps: 71 | - uses: actions/checkout@v4 72 | 73 | - name: Build sdist 74 | run: pipx run build --sdist 75 | 76 | - uses: actions/upload-artifact@v4 77 | with: 78 | name: cibw-sdist 79 | path: dist/*.tar.gz 80 | 81 | lint: 82 | name: Lint with flake8 and check black 83 | runs-on: ubuntu-latest 84 | steps: 85 | - uses: actions/checkout@v4 86 | 87 | - name: Lint with flake8 and check black 88 | run: | 89 | pip install black flake8 90 | flake8 . --filename '*.py,*.pyx,*.pxd' --count --show-source --statistics --exclude benchmark,build,capnp/templates/module.pyx 91 | flake8 . --count --show-source --statistics --exclude benchmark,build 92 | black . --check --diff --color 93 | 94 | # upload_pypi: 95 | # needs: [build_wheels, build_sdist] 96 | # runs-on: ubuntu-latest 97 | # if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') 98 | # steps: 99 | # - uses: actions/download-artifact@v3 100 | # with: 101 | # # unpacks default artifact into dist/ 102 | # # if `name: artifact` is omitted, the action will create extra parent dir 103 | # name: artifact 104 | # path: dist 105 | 106 | # - uses: pypa/gh-action-pypi-publish@v1.5.0 107 | # with: 108 | # user: __token__ 109 | # password: ${{ secrets.PYPI_PASSWORD_RELEASE }} 110 | 111 | # # password: ${{ secrets.PYPI_PASSWORD }} 112 | # # repository_url: https://test.pypi.org/legacy/ 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | build32 12 | build64 13 | eggs 14 | parts 15 | bin 16 | var 17 | sdist 18 | develop-eggs 19 | .installed.cfg 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | 37 | # IntelliJ 38 | .idea/ 39 | 40 | # Cpp files 41 | capnp/*.cpp 42 | 43 | capnp/version.py 44 | MANIFEST 45 | docs/_build 46 | 47 | capnp/lib/capnp.cpp 48 | capnp/lib/capnp.h 49 | capnp/lib/capnp_api.h 50 | bundled/ 51 | example 52 | *.iml 53 | 54 | # capnp files 55 | *.capnp 56 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Jason Paryani 2 | Copyright (c) 2019-2020, Jacob Alexander 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE.md 3 | include CHANGELOG.md 4 | include requirements.txt 5 | include buildutils/* 6 | include _custom_build/* 7 | include capnp/lib/capnp_api.h 8 | include Pipfile 9 | include tox.ini 10 | recursive-include examples *.capnp 11 | recursive-include examples *.cert 12 | recursive-include examples *.key 13 | recursive-include examples *.py 14 | recursive-include test *.binary 15 | recursive-include test *.capnp 16 | recursive-include test *.packed 17 | recursive-include test *.txt 18 | recursive-include test *.py 19 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [packages] 7 | Cython = "<3" 8 | Jinja2 = "*" 9 | black = "*" 10 | flake8 = "*" 11 | pkgconfig = "*" 12 | pytest = "*" 13 | sphinx = "*" 14 | sphinx-multiversion = "*" 15 | tox = "*" 16 | wheel = "*" 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pycapnp 2 | 3 | [![Packaging Status](https://github.com/capnproto/pycapnp/workflows/Packaging%20Test/badge.svg)](https://github.com/capnproto/pycapnp/actions) 4 | [![manylinux2014 Status](https://github.com/capnproto/pycapnp/workflows/manylinux2014/badge.svg)](https://github.com/capnproto/pycapnp/actions) 5 | [![PyPI version](https://badge.fury.io/py/pycapnp.svg)](https://badge.fury.io/py/pycapnp) 6 | 7 | [Cap'n'proto Mailing List](https://github.com/capnproto/capnproto/discussions) [Documentation](https://capnproto.github.io/pycapnp) 8 | 9 | 10 | ## Requirements 11 | 12 | * C++14 supported compiler 13 | - gcc 6.1+ (5+ may work) 14 | - clang 6 (3.4+ may work) 15 | - Visual Studio 2017+ 16 | * cmake (needed for bundled capnproto) 17 | - ninja (macOS + Linux) 18 | - Visual Studio 2017+ 19 | * capnproto-1.0 (>=0.8.0 will also work if linking to system libraries) 20 | - Not necessary if using bundled capnproto 21 | * Python development headers (i.e. Python.h) 22 | - Distributables from python.org include these, however they are usually in a separate package on Linux distributions 23 | 24 | 32-bit Linux requires that capnproto be compiled with `-fPIC`. This is usually set correctly unless you are compiling canproto yourself. This is also called `-DCMAKE_POSITION_INDEPENDENT_CODE=1` for cmake. 25 | 26 | pycapnp has additional development dependencies, including cython and pytest. See requirements.txt for them all. 27 | 28 | 29 | ## Building and installation 30 | 31 | Install with `pip install pycapnp`. You can set the CC environment variable to control which compiler is used, ie `CC=gcc-8.2 pip install pycapnp`. 32 | 33 | Or you can clone the repo like so: 34 | 35 | ```bash 36 | git clone https://github.com/capnproto/pycapnp.git 37 | cd pycapnp 38 | pip install . 39 | ``` 40 | 41 | By default, the setup script will automatically use the locally installed Cap'n Proto. 42 | If Cap'n Proto is not installed, it will bundle and build the matching Cap'n Proto library. 43 | 44 | To enforce bundling, the Cap'n Proto library: 45 | 46 | ```bash 47 | pip install . -C force-bundled-libcapnp=True 48 | ``` 49 | 50 | If you wish to install using the latest upstream C++ Cap'n Proto: 51 | 52 | ```bash 53 | pip install . \ 54 | -C force-bundled-libcapnp=True \ 55 | -C libcapnp-url="https://github.com/capnproto/capnproto/archive/master.tar.gz" 56 | ``` 57 | 58 | To enforce using the installed Cap'n Proto from the system: 59 | 60 | ```bash 61 | pip install . -C force-system-libcapnp=True 62 | ``` 63 | 64 | The bundling system isn't that smart so it might be necessary to clean up the bundled build when changing versions: 65 | 66 | ```bash 67 | python setup.py clean 68 | ``` 69 | 70 | 71 | ## Stub-file generation 72 | 73 | While not directly supported by pycapnp, a tool has been created to help generate pycapnp stubfile to assist with development (this is very helpful if you're new to pypcapnp!). See [#289](https://github.com/capnproto/pycapnp/pull/289#event-9078216721) for more details. 74 | 75 | [Python Capnp Stub Generator](https://gitlab.com/mic_public/tools/python-helpers/capnp-stub-generator) 76 | 77 | 78 | ## Python Versions 79 | 80 | Python 3.8+ is supported. 81 | 82 | 83 | ## Development 84 | 85 | Git flow has been abandoned, use master. 86 | 87 | To test, use a pipenv (or install requirements.txt and run pytest manually). 88 | ```bash 89 | pip install pipenv 90 | pipenv install 91 | pipenv run pytest 92 | ``` 93 | 94 | 95 | ### Binary Packages 96 | 97 | Building a Python wheel distributiion 98 | 99 | ```bash 100 | pip wheel . 101 | ``` 102 | 103 | ## Documentation/Example 104 | 105 | There is some basic documentation [here](http://capnproto.github.io/pycapnp/). 106 | 107 | Make sure to look at the [examples](examples). The examples are generally kept up to date with the recommended usage of the library. 108 | 109 | The examples directory has one example that shows off pycapnp quite nicely. Here it is, reproduced: 110 | 111 | ```python 112 | import os 113 | import capnp 114 | 115 | import addressbook_capnp 116 | 117 | def writeAddressBook(file): 118 | addresses = addressbook_capnp.AddressBook.new_message() 119 | people = addresses.init('people', 2) 120 | 121 | alice = people[0] 122 | alice.id = 123 123 | alice.name = 'Alice' 124 | alice.email = 'alice@example.com' 125 | alicePhones = alice.init('phones', 1) 126 | alicePhones[0].number = "555-1212" 127 | alicePhones[0].type = 'mobile' 128 | alice.employment.school = "MIT" 129 | 130 | bob = people[1] 131 | bob.id = 456 132 | bob.name = 'Bob' 133 | bob.email = 'bob@example.com' 134 | bobPhones = bob.init('phones', 2) 135 | bobPhones[0].number = "555-4567" 136 | bobPhones[0].type = 'home' 137 | bobPhones[1].number = "555-7654" 138 | bobPhones[1].type = 'work' 139 | bob.employment.unemployed = None 140 | 141 | addresses.write(file) 142 | 143 | 144 | def printAddressBook(file): 145 | addresses = addressbook_capnp.AddressBook.read(file) 146 | 147 | for person in addresses.people: 148 | print(person.name, ':', person.email) 149 | for phone in person.phones: 150 | print(phone.type, ':', phone.number) 151 | 152 | which = person.employment.which() 153 | print(which) 154 | 155 | if which == 'unemployed': 156 | print('unemployed') 157 | elif which == 'employer': 158 | print('employer:', person.employment.employer) 159 | elif which == 'school': 160 | print('student at:', person.employment.school) 161 | elif which == 'selfEmployed': 162 | print('self employed') 163 | print() 164 | 165 | 166 | if __name__ == '__main__': 167 | f = open('example', 'w') 168 | writeAddressBook(f) 169 | 170 | f = open('example', 'r') 171 | printAddressBook(f) 172 | ``` 173 | 174 | Also, pycapnp has gained RPC features that include pipelining and a promise style API. Refer to the calculator example in the examples directory for a much better demonstration: 175 | 176 | ```python 177 | import asyncio 178 | import capnp 179 | import socket 180 | 181 | import test_capability_capnp 182 | 183 | 184 | class Server(test_capability_capnp.TestInterface.Server): 185 | 186 | def __init__(self, val=1): 187 | self.val = val 188 | 189 | async def foo(self, i, j, **kwargs): 190 | return str(i * 5 + self.val) 191 | 192 | 193 | async def client(read_end): 194 | client = capnp.TwoPartyClient(read_end) 195 | 196 | cap = client.bootstrap() 197 | cap = cap.cast_as(test_capability_capnp.TestInterface) 198 | 199 | remote = cap.foo(i=5) 200 | response = await remote 201 | 202 | assert response.x == '125' 203 | 204 | async def main(): 205 | client_end, server_end = socket.socketpair(socket.AF_UNIX) 206 | # This is a toy example using socketpair. 207 | # In real situations, you can use any socket. 208 | 209 | client_end = await capnp.AsyncIoStream.create_connection(sock=client_end) 210 | server_end = await capnp.AsyncIoStream.create_connection(sock=server_end) 211 | 212 | _ = capnp.TwoPartyServer(server_end, bootstrap=Server(100)) 213 | await client(client_end) 214 | 215 | 216 | if __name__ == '__main__': 217 | asyncio.run(capnp.run(main())) 218 | ``` 219 | -------------------------------------------------------------------------------- /_custom_build/backend.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from setuptools.build_meta import * # noqa: F401, F403 4 | from setuptools.build_meta import build_wheel 5 | 6 | backend_class = build_wheel.__self__.__class__ 7 | 8 | 9 | class _CustomBuildMetaBackend(backend_class): 10 | def run_setup(self, setup_script="setup.py"): 11 | if self.config_settings: 12 | flags = [] 13 | if self.config_settings.get("force-bundled-libcapnp"): 14 | flags.append("--force-bundled-libcapnp") 15 | if self.config_settings.get("force-system-libcapnp"): 16 | flags.append("--force-system-libcapnp") 17 | if self.config_settings.get("libcapnp-url"): 18 | flags.append("--libcapnp-url") 19 | flags.append(self.config_settings["libcapnp-url"]) 20 | if flags: 21 | sys.argv = sys.argv[:1] + ["build_ext"] + flags + sys.argv[1:] 22 | return super().run_setup(setup_script) 23 | 24 | def build_wheel( 25 | self, wheel_directory, config_settings=None, metadata_directory=None 26 | ): 27 | self.config_settings = config_settings 28 | return super().build_wheel(wheel_directory, config_settings, metadata_directory) 29 | 30 | 31 | build_wheel = _CustomBuildMetaBackend().build_wheel 32 | -------------------------------------------------------------------------------- /benchmark/addressbook.capnp: -------------------------------------------------------------------------------- 1 | @0x934efea7f017fff0; 2 | 3 | struct Person { 4 | id @0 :UInt32; 5 | name @1 :Text; 6 | email @2 :Text; 7 | phones @3 :List(PhoneNumber); 8 | 9 | struct PhoneNumber { 10 | number @0 :Text; 11 | type @1 :Type; 12 | 13 | enum Type { 14 | mobile @0; 15 | home @1; 16 | work @2; 17 | } 18 | } 19 | } 20 | 21 | struct AddressBook { 22 | people @0 :List(Person); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /benchmark/addressbook.capnp.orphan.py: -------------------------------------------------------------------------------- 1 | import os 2 | import capnp 3 | 4 | this_dir = os.path.dirname(__file__) 5 | addressbook = capnp.load(os.path.join(this_dir, "addressbook.capnp")) 6 | 7 | print = lambda *x: x 8 | 9 | 10 | def writeAddressBook(): 11 | addressBook = addressbook.AddressBook.new_message() 12 | people = addressBook.init_resizable_list("people") 13 | 14 | alice = people.add() 15 | alice.id = 123 16 | alice.name = "Alice" 17 | alice.email = "alice@example.com" 18 | alicePhones = alice.init("phones", 1) 19 | alicePhones[0].number = "555-1212" 20 | alicePhones[0].type = "mobile" 21 | 22 | bob = people.add() 23 | bob.id = 456 24 | bob.name = "Bob" 25 | bob.email = "bob@example.com" 26 | bobPhones = bob.init("phones", 2) 27 | bobPhones[0].number = "555-4567" 28 | bobPhones[0].type = "home" 29 | bobPhones[1].number = "555-7654" 30 | bobPhones[1].type = "work" 31 | 32 | people.finish() 33 | msg_bytes = addressBook.to_bytes() 34 | return msg_bytes 35 | 36 | 37 | def printAddressBook(msg_bytes): 38 | with addressbook.AddressBook.from_bytes(msg_bytes) as addressBook: 39 | for person in addressBook.people: 40 | print(person.name, ":", person.email) 41 | for phone in person.phones: 42 | print(phone.type, ":", phone.number) 43 | print() 44 | 45 | 46 | if __name__ == "__main__": 47 | for i in range(10000): 48 | msg_bytes = writeAddressBook() 49 | 50 | printAddressBook(msg_bytes) 51 | -------------------------------------------------------------------------------- /benchmark/addressbook.capnp.py: -------------------------------------------------------------------------------- 1 | import os 2 | import capnp 3 | 4 | try: 5 | profile 6 | except: 7 | profile = lambda func: func 8 | this_dir = os.path.dirname(__file__) 9 | addressbook = capnp.load(os.path.join(this_dir, "addressbook.capnp")) 10 | 11 | print = lambda *x: x 12 | 13 | 14 | @profile 15 | def writeAddressBook(): 16 | addressBook = addressbook.AddressBook.new_message() 17 | people = addressBook.init("people", 2) 18 | 19 | alice = people[0] 20 | alice.id = 123 21 | alice.name = "Alice" 22 | alice.email = "alice@example.com" 23 | alicePhones = alice.init("phones", 1) 24 | alicePhones[0].number = "555-1212" 25 | alicePhones[0].type = "mobile" 26 | 27 | bob = people[1] 28 | bob.id = 456 29 | bob.name = "Bob" 30 | bob.email = "bob@example.com" 31 | bobPhones = bob.init("phones", 2) 32 | bobPhones[0].number = "555-4567" 33 | bobPhones[0].type = "home" 34 | bobPhones[1].number = "555-7654" 35 | bobPhones[1].type = "work" 36 | 37 | msg_bytes = addressBook.to_bytes() 38 | return msg_bytes 39 | 40 | 41 | @profile 42 | def printAddressBook(msg_bytes): 43 | with addressbook.AddressBook.from_bytes(msg_bytes) as addressBook: 44 | for person in addressBook.people: 45 | person.name, person.email 46 | for phone in person.phones: 47 | phone.type, phone.number 48 | 49 | 50 | @profile 51 | def writeAddressBookDict(): 52 | addressBook = addressbook.AddressBook.new_message() 53 | people = addressBook.init("people", 2) 54 | 55 | alice = people[0] 56 | alice.id = 123 57 | alice.name = "Alice" 58 | alice.email = "alice@example.com" 59 | alicePhones = alice.init("phones", 1) 60 | alicePhones[0].number = "555-1212" 61 | alicePhones[0].type = "mobile" 62 | 63 | bob = people[1] 64 | bob.id = 456 65 | bob.name = "Bob" 66 | bob.email = "bob@example.com" 67 | bobPhones = bob.init("phones", 2) 68 | bobPhones[0].number = "555-4567" 69 | bobPhones[0].type = "home" 70 | bobPhones[1].number = "555-7654" 71 | bobPhones[1].type = "work" 72 | 73 | msg = addressBook.to_dict() 74 | return msg 75 | 76 | 77 | @profile 78 | def printAddressBookDict(msg): 79 | addressBook = addressbook.AddressBook.new_message(**msg) 80 | 81 | for person in addressBook.people: 82 | person.name, person.email 83 | for phone in person.phones: 84 | phone.type, phone.number 85 | 86 | 87 | if __name__ == "__main__": 88 | # for i in range(10000): 89 | # msg_bytes = writeAddressBook() 90 | 91 | # printAddressBook(msg_bytes) 92 | for i in range(10000): 93 | msg = writeAddressBookDict() 94 | 95 | printAddressBookDict(msg) 96 | -------------------------------------------------------------------------------- /benchmark/addressbook.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package tutorial; 4 | 5 | message Person { 6 | required string name = 1; 7 | required int32 id = 2; 8 | required string email = 3; 9 | 10 | enum PhoneType { 11 | MOBILE = 0; 12 | HOME = 1; 13 | WORK = 2; 14 | } 15 | 16 | message PhoneNumber { 17 | required string number = 1; 18 | optional PhoneType type = 2 [default = HOME]; 19 | } 20 | 21 | repeated PhoneNumber phone = 4; 22 | } 23 | 24 | message AddressBook { 25 | repeated Person person = 1; 26 | } 27 | -------------------------------------------------------------------------------- /benchmark/addressbook.proto.py: -------------------------------------------------------------------------------- 1 | import addressbook_pb2 as addressbook 2 | import os 3 | 4 | print = lambda *x: x 5 | 6 | 7 | def writeAddressBook(): 8 | addressBook = addressbook.AddressBook() 9 | 10 | alice = addressBook.person.add() 11 | alice.id = 123 12 | alice.name = "Alice" 13 | alice.email = "alice@example.com" 14 | alicePhones = [alice.phone.add()] 15 | alicePhones[0].number = "555-1212" 16 | alicePhones[0].type = addressbook.Person.MOBILE 17 | 18 | bob = addressBook.person.add() 19 | bob.id = 456 20 | bob.name = "Bob" 21 | bob.email = "bob@example.com" 22 | bobPhones = [bob.phone.add(), bob.phone.add()] 23 | bobPhones[0].number = "555-4567" 24 | bobPhones[0].type = addressbook.Person.HOME 25 | bobPhones[1].number = "555-7654" 26 | bobPhones[1].type = addressbook.Person.WORK 27 | 28 | message_string = addressBook.SerializeToString() 29 | return message_string 30 | 31 | 32 | def printAddressBook(message_string): 33 | addressBook = addressbook.AddressBook() 34 | addressBook.ParseFromString(message_string) 35 | 36 | for person in addressBook.person: 37 | print(person.name, ":", person.email) 38 | for phone in person.phone: 39 | print(phone.type, ":", phone.number) 40 | print() 41 | 42 | 43 | if __name__ == "__main__": 44 | for i in range(10000): 45 | message_string = writeAddressBook() 46 | 47 | printAddressBook(message_string) 48 | -------------------------------------------------------------------------------- /benchmark/bin/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | You'll need to install the protobuf dependencies if you want to profile against protobufs. 4 | 5 | ```bash 6 | pip install -r requirements.txt 7 | ``` 8 | 9 | To run all the benchmarks: 10 | 11 | ```bash 12 | ./run_all -l pyproto -l pyproto_cpp 13 | ``` 14 | -------------------------------------------------------------------------------- /benchmark/bin/pycapnp-carsales: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from runner import parse_args_simple, run_test 4 | 5 | def main(): 6 | args = parse_args_simple() 7 | run_test(name='carsales', suffix='pycapnp', **vars(args)) 8 | 9 | if __name__ == '__main__': 10 | main() -------------------------------------------------------------------------------- /benchmark/bin/pycapnp-catrank: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from runner import parse_args_simple, run_test 4 | 5 | def main(): 6 | args = parse_args_simple() 7 | run_test(name='catrank', suffix='pycapnp', **vars(args)) 8 | 9 | if __name__ == '__main__': 10 | main() -------------------------------------------------------------------------------- /benchmark/bin/pycapnp-eval: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from runner import parse_args_simple, run_test 4 | 5 | def main(): 6 | args = parse_args_simple() 7 | run_test(name='eval', suffix='pycapnp', **vars(args)) 8 | 9 | if __name__ == '__main__': 10 | main() -------------------------------------------------------------------------------- /benchmark/bin/pyproto-carsales: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from runner import parse_args_simple, run_test 4 | 5 | def main(): 6 | args = parse_args_simple() 7 | run_test(name='carsales', suffix='proto', **vars(args)) 8 | 9 | if __name__ == '__main__': 10 | main() -------------------------------------------------------------------------------- /benchmark/bin/pyproto-catrank: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from runner import parse_args_simple, run_test 4 | 5 | def main(): 6 | args = parse_args_simple() 7 | run_test(name='catrank', suffix='proto', **vars(args)) 8 | 9 | if __name__ == '__main__': 10 | main() -------------------------------------------------------------------------------- /benchmark/bin/pyproto-eval: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from runner import parse_args_simple, run_test 4 | 5 | def main(): 6 | args = parse_args_simple() 7 | run_test(name='eval', suffix='proto', **vars(args)) 8 | 9 | if __name__ == '__main__': 10 | main() -------------------------------------------------------------------------------- /benchmark/bin/pyproto_cpp-carsales: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp 4 | pyproto-carsales $@ 5 | -------------------------------------------------------------------------------- /benchmark/bin/pyproto_cpp-catrank: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp 4 | pyproto-catrank $@ 5 | -------------------------------------------------------------------------------- /benchmark/bin/pyproto_cpp-eval: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp 4 | pyproto-eval $@ 5 | -------------------------------------------------------------------------------- /benchmark/bin/requirements.txt: -------------------------------------------------------------------------------- 1 | protobuf 2 | -------------------------------------------------------------------------------- /benchmark/bin/run_all.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | 4 | from subprocess import Popen, PIPE 5 | import sys 6 | import os 7 | import json 8 | import argparse 9 | import time 10 | 11 | _this_dir = os.path.dirname(__file__) 12 | 13 | 14 | def parse_args(): 15 | parser = argparse.ArgumentParser() 16 | parser.add_argument( 17 | "-l", 18 | "--langs", 19 | help="Add languages to test, ie: -l pyproto -l pyproto_cpp", 20 | action="append", 21 | default=["pycapnp"], 22 | ) 23 | parser.add_argument( 24 | "-r", 25 | "--reuse", 26 | help="If this flag is passed, re-use tests will be run", 27 | action="store_true", 28 | ) 29 | parser.add_argument( 30 | "-c", 31 | "--compression", 32 | help="If this flag is passed, compression tests will be run", 33 | action="store_true", 34 | ) 35 | parser.add_argument( 36 | "-i", 37 | "--scale_iters", 38 | help="Scaling factor to multiply the default iters by", 39 | type=float, 40 | default=1.0, 41 | ) 42 | 43 | return parser.parse_args() 44 | 45 | 46 | def run_one(prefix, name, mode, iters, faster, compression): 47 | res_type = prefix 48 | reuse = "no-reuse" 49 | 50 | if faster: 51 | reuse = "reuse" 52 | res_type += "_reuse" 53 | if compression != "none": 54 | res_type += "_" + compression 55 | 56 | command = [ 57 | os.path.join(_this_dir, prefix + "-" + name), 58 | mode, 59 | reuse, 60 | compression, 61 | str(iters), 62 | ] 63 | start = time.time() 64 | print("running: " + " ".join(command), file=sys.stderr) 65 | p = Popen(command, stdout=PIPE, stderr=PIPE) 66 | res = p.wait() 67 | end = time.time() 68 | 69 | data = {} 70 | 71 | if p.returncode != 0: 72 | sys.stderr.write( 73 | " ".join(command) 74 | + " failed to run with errors: \n" 75 | + p.stderr.read().decode(sys.stdout.encoding) 76 | + "\n" 77 | ) 78 | sys.stderr.flush() 79 | 80 | data["type"] = res_type 81 | data["mode"] = mode 82 | data["name"] = name 83 | data["iters"] = iters 84 | data["time"] = end - start 85 | 86 | return data 87 | 88 | 89 | def run_each(name, langs, reuse, compression, iters): 90 | ret = [] 91 | 92 | for lang_name in langs: 93 | ret.append(run_one(lang_name, name, "object", iters, False, "none")) 94 | ret.append(run_one(lang_name, name, "bytes", iters, False, "none")) 95 | if reuse: 96 | ret.append(run_one(lang_name, name, "object", iters, True, "none")) 97 | ret.append(run_one(lang_name, name, "bytes", iters, True, "none")) 98 | if compression: 99 | ret.append(run_one(lang_name, name, "bytes", iters, True, "packed")) 100 | if compression: 101 | ret.append(run_one(lang_name, name, "bytes", iters, False, "packed")) 102 | 103 | return ret 104 | 105 | 106 | def main(): 107 | args = parse_args() 108 | 109 | os.environ["PATH"] += ":." 110 | 111 | data = [] 112 | data += run_each( 113 | "carsales", 114 | args.langs, 115 | args.reuse, 116 | args.compression, 117 | int(2000 * args.scale_iters), 118 | ) 119 | data += run_each( 120 | "catrank", args.langs, args.reuse, args.compression, int(100 * args.scale_iters) 121 | ) 122 | data += run_each( 123 | "eval", args.langs, args.reuse, args.compression, int(10000 * args.scale_iters) 124 | ) 125 | json.dump(data, sys.stdout, sort_keys=True, indent=4, separators=(",", ": ")) 126 | 127 | 128 | if __name__ == "__main__": 129 | main() 130 | -------------------------------------------------------------------------------- /benchmark/bin/runner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | import sys 5 | import os 6 | from importlib import import_module 7 | from timeit import default_timer 8 | import random 9 | 10 | _this_dir = os.path.dirname(__file__) 11 | sys.path.append(os.path.join(_this_dir, "..")) 12 | from common import do_benchmark 13 | 14 | 15 | def parse_args_simple(): 16 | parser = argparse.ArgumentParser() 17 | parser.add_argument( 18 | "mode", help="Mode to use for serialization, ie. object or bytes" 19 | ) 20 | parser.add_argument("reuse", help="Currently ignored") 21 | parser.add_argument("compression", help="Valid values are none or packed") 22 | parser.add_argument("iters", help="Number of iterations to run for", type=int) 23 | parser.add_argument( 24 | "-I", 25 | "--includes", 26 | help="Directories to add to PYTHONPATH", 27 | default="/usr/local/include", 28 | ) 29 | 30 | return parser.parse_args() 31 | 32 | 33 | def parse_args(): 34 | parser = argparse.ArgumentParser() 35 | parser.add_argument( 36 | "name", 37 | help="Name of the benchmark to run, eg. carsales", 38 | nargs="?", 39 | default="carsales", 40 | ) 41 | parser.add_argument( 42 | "-c", "--compression", help="Specify the compression type", default=None 43 | ) 44 | parser.add_argument( 45 | "-s", "--suffix", help="Choose the protocol type.", default="pycapnp" 46 | ) 47 | parser.add_argument("-m", "--mode", help="Specify the mode", default="object") 48 | parser.add_argument( 49 | "-i", 50 | "--iters", 51 | help="Specify the number of iterations manually. By default, it will be looked up in preset table", 52 | default=10, 53 | type=int, 54 | ) 55 | parser.add_argument( 56 | "-r", 57 | "--reuse", 58 | help="If this flag is passed, objects will be re-used", 59 | action="store_true", 60 | ) 61 | parser.add_argument( 62 | "-I", 63 | "--includes", 64 | help="Directories to add to PYTHONPATH", 65 | default="/usr/local/include", 66 | ) 67 | 68 | return parser.parse_args() 69 | 70 | 71 | def run_test(name, mode, reuse, compression, iters, suffix, includes): 72 | tic = default_timer() 73 | 74 | name = name 75 | sys.path.append(includes) 76 | module = import_module(name + "_" + suffix) 77 | benchmark = module.Benchmark(compression=compression) 78 | 79 | do_benchmark(mode=mode, benchmark=benchmark, iters=iters, reuse=reuse) 80 | toc = default_timer() 81 | return toc - tic 82 | 83 | 84 | def main(): 85 | args = parse_args() 86 | run_test(**vars(args)) 87 | 88 | 89 | if __name__ == "__main__": 90 | main() 91 | -------------------------------------------------------------------------------- /benchmark/carsales.capnp: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013, Kenton Varda 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright notice, 10 | # this list of conditions and the following disclaimer in the documentation 11 | # and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | using Cxx = import "/capnp/c++.capnp"; 25 | 26 | @0xff75ddc6a36723c9; 27 | $Cxx.namespace("capnp::benchmark::capnp"); 28 | 29 | struct ParkingLot { 30 | cars@0: List(Car); 31 | } 32 | 33 | struct TotalValue { 34 | amount@0: UInt64; 35 | } 36 | 37 | struct Car { 38 | make@0: Text; 39 | model@1: Text; 40 | color@2: Color; 41 | seats@3: UInt8; 42 | doors@4: UInt8; 43 | wheels@5: List(Wheel); 44 | length@6: UInt16; 45 | width@7: UInt16; 46 | height@8: UInt16; 47 | weight@9: UInt32; 48 | engine@10: Engine; 49 | fuelCapacity@11: Float32; 50 | fuelLevel@12: Float32; 51 | hasPowerWindows@13: Bool; 52 | hasPowerSteering@14: Bool; 53 | hasCruiseControl@15: Bool; 54 | cupHolders@16: UInt8; 55 | hasNavSystem@17: Bool; 56 | } 57 | 58 | enum Color { 59 | black @0; 60 | white @1; 61 | red @2; 62 | green @3; 63 | blue @4; 64 | cyan @5; 65 | magenta @6; 66 | yellow @7; 67 | silver @8; 68 | } 69 | 70 | struct Wheel { 71 | diameter@0: UInt16; 72 | airPressure@1: Float32; 73 | snowTires@2: Bool; 74 | } 75 | 76 | struct Engine { 77 | horsepower@0: UInt16; 78 | cylinders@1: UInt8; 79 | cc@2: UInt32; 80 | usesGas@3: Bool; 81 | usesElectric@4: Bool; 82 | } 83 | -------------------------------------------------------------------------------- /benchmark/carsales.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, Kenton Varda 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright notice, this 8 | // list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation 11 | // and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | syntax = "proto2"; 25 | 26 | package capnp.benchmark.protobuf; 27 | 28 | message ParkingLot { 29 | repeated Car car = 1; 30 | } 31 | 32 | message TotalValue { 33 | required uint64 amount = 1; 34 | } 35 | 36 | message Car { 37 | optional string make = 1; 38 | optional string model = 2; 39 | optional Color color = 3; 40 | optional uint32 seats = 4; 41 | optional uint32 doors = 5; 42 | repeated Wheel wheel = 6; 43 | optional uint32 length = 7; 44 | optional uint32 width = 8; 45 | optional uint32 height = 9; 46 | optional uint32 weight = 10; 47 | optional Engine engine = 11; 48 | optional float fuel_capacity = 12; 49 | optional float fuel_level = 13; 50 | optional bool has_power_windows = 14; 51 | optional bool has_power_steering = 15; 52 | optional bool has_cruise_control = 16; 53 | optional uint32 cup_holders = 17; 54 | optional bool has_nav_system = 18; 55 | } 56 | 57 | enum Color { 58 | BLACK = 0; 59 | WHITE = 1; 60 | RED = 2; 61 | GREEN = 3; 62 | BLUE = 4; 63 | CYAN = 5; 64 | MAGENTA = 6; 65 | YELLOW = 7; 66 | SILVER = 8; 67 | } 68 | 69 | message Wheel { 70 | optional uint32 diameter = 1; 71 | optional float air_pressure = 2; 72 | optional bool snow_tires = 3; 73 | } 74 | 75 | message Engine { 76 | optional uint32 horsepower = 1; 77 | optional uint32 cylinders = 2; 78 | optional uint32 cc = 3; 79 | optional bool uses_gas = 4; 80 | optional bool uses_electric = 5; 81 | } 82 | -------------------------------------------------------------------------------- /benchmark/carsales_proto.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import carsales_pb2 4 | from common import rand_int, rand_double, rand_bool, from_bytes_helper 5 | from random import choice 6 | 7 | MAKES = ["Toyota", "GM", "Ford", "Honda", "Tesla"] 8 | MODELS = ["Camry", "Prius", "Volt", "Accord", "Leaf", "Model S"] 9 | COLORS = [ 10 | "black", 11 | "white", 12 | "red", 13 | "green", 14 | "blue", 15 | "cyan", 16 | "magenta", 17 | "yellow", 18 | "silver", 19 | ] 20 | 21 | 22 | def random_car(car): 23 | car.make = choice(MAKES) 24 | car.model = choice(MODELS) 25 | car.color = rand_int(len(COLORS)) 26 | 27 | car.seats = 2 + rand_int(6) 28 | car.doors = 2 + rand_int(3) 29 | 30 | for _ in range(4): 31 | wheel = car.wheel.add() 32 | wheel.diameter = 25 + rand_int(15) 33 | wheel.air_pressure = 30 + rand_double(20) 34 | wheel.snow_tires = rand_int(16) == 0 35 | 36 | car.length = 170 + rand_int(150) 37 | car.width = 48 + rand_int(36) 38 | car.height = 54 + rand_int(48) 39 | car.weight = car.length * car.width * car.height // 200 40 | 41 | engine = car.engine 42 | engine.horsepower = 100 * rand_int(400) 43 | engine.cylinders = 4 + 2 * rand_int(3) 44 | engine.cc = 800 + rand_int(10000) 45 | engine.uses_gas = True 46 | engine.uses_electric = rand_bool() 47 | 48 | car.fuel_capacity = 10.0 + rand_double(30.0) 49 | car.fuel_level = rand_double(car.fuel_capacity) 50 | car.has_power_windows = rand_bool() 51 | car.has_power_steering = rand_bool() 52 | car.has_cruise_control = rand_bool() 53 | car.cup_holders = rand_int(12) 54 | car.has_nav_system = rand_bool() 55 | 56 | 57 | def calc_value(car): 58 | result = 0 59 | 60 | result += car.seats * 200 61 | result += car.doors * 350 62 | for wheel in car.wheel: 63 | result += wheel.diameter * wheel.diameter 64 | result += 100 if wheel.snow_tires else 0 65 | 66 | result += car.length * car.width * car.height // 50 67 | 68 | engine = car.engine 69 | result += engine.horsepower * 40 70 | if engine.uses_electric: 71 | if engine.uses_gas: 72 | result += 5000 73 | else: 74 | result += 3000 75 | 76 | result += 100 if car.has_power_windows else 0 77 | result += 200 if car.has_power_steering else 0 78 | result += 400 if car.has_cruise_control else 0 79 | result += 2000 if car.has_nav_system else 0 80 | 81 | result += car.cup_holders * 25 82 | 83 | return result 84 | 85 | 86 | class Benchmark: 87 | def __init__(self, compression): 88 | self.Request = carsales_pb2.ParkingLot 89 | self.Response = carsales_pb2.TotalValue 90 | self.from_bytes_request = from_bytes_helper(carsales_pb2.ParkingLot) 91 | self.from_bytes_response = from_bytes_helper(carsales_pb2.TotalValue) 92 | self.to_bytes = lambda x: x.SerializeToString() 93 | 94 | def setup(self, request): 95 | result = 0 96 | for _ in range(rand_int(200)): 97 | car = request.car.add() 98 | random_car(car) 99 | result += calc_value(car) 100 | return result 101 | 102 | def handle(self, request, response): 103 | result = 0 104 | for car in request.car: 105 | result += calc_value(car) 106 | 107 | response.amount = result 108 | 109 | def check(self, response, expected): 110 | return response.amount == expected 111 | -------------------------------------------------------------------------------- /benchmark/carsales_pycapnp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import capnp 4 | import carsales_capnp 5 | from common import rand_int, rand_double, rand_bool 6 | from random import choice 7 | 8 | MAKES = ["Toyota", "GM", "Ford", "Honda", "Tesla"] 9 | MODELS = ["Camry", "Prius", "Volt", "Accord", "Leaf", "Model S"] 10 | COLORS = [ 11 | "black", 12 | "white", 13 | "red", 14 | "green", 15 | "blue", 16 | "cyan", 17 | "magenta", 18 | "yellow", 19 | "silver", 20 | ] 21 | 22 | 23 | def random_car(car): 24 | car.make = choice(MAKES) 25 | car.model = choice(MODELS) 26 | car.color = choice(COLORS) 27 | 28 | car.seats = 2 + rand_int(6) 29 | car.doors = 2 + rand_int(3) 30 | 31 | for wheel in car.init("wheels", 4): 32 | wheel.diameter = 25 + rand_int(15) 33 | wheel.airPressure = 30 + rand_double(20) 34 | wheel.snowTires = rand_int(16) == 0 35 | 36 | car.length = 170 + rand_int(150) 37 | car.width = 48 + rand_int(36) 38 | car.height = 54 + rand_int(48) 39 | car.weight = car.length * car.width * car.height // 200 40 | 41 | engine = car.init("engine") 42 | engine.horsepower = 100 * rand_int(400) 43 | engine.cylinders = 4 + 2 * rand_int(3) 44 | engine.cc = 800 + rand_int(10000) 45 | engine.usesGas = True 46 | engine.usesElectric = rand_bool() 47 | 48 | car.fuelCapacity = 10.0 + rand_double(30.0) 49 | car.fuelLevel = rand_double(car.fuelCapacity) 50 | car.hasPowerWindows = rand_bool() 51 | car.hasPowerSteering = rand_bool() 52 | car.hasCruiseControl = rand_bool() 53 | car.cupHolders = rand_int(12) 54 | car.hasNavSystem = rand_bool() 55 | 56 | 57 | def calc_value(car): 58 | result = 0 59 | 60 | result += car.seats * 200 61 | result += car.doors * 350 62 | for wheel in car.wheels: 63 | result += wheel.diameter * wheel.diameter 64 | result += 100 if wheel.snowTires else 0 65 | 66 | result += car.length * car.width * car.height // 50 67 | 68 | engine = car.engine 69 | result += engine.horsepower * 40 70 | if engine.usesElectric: 71 | if engine.usesGas: 72 | result += 5000 73 | else: 74 | result += 3000 75 | 76 | result += 100 if car.hasPowerWindows else 0 77 | result += 200 if car.hasPowerSteering else 0 78 | result += 400 if car.hasCruiseControl else 0 79 | result += 2000 if car.hasNavSystem else 0 80 | 81 | result += car.cupHolders * 25 82 | 83 | return result 84 | 85 | 86 | class Benchmark: 87 | def __init__(self, compression): 88 | self.Request = carsales_capnp.ParkingLot.new_message 89 | self.Response = carsales_capnp.TotalValue.new_message 90 | if compression == "packed": 91 | self.from_bytes_request = carsales_capnp.ParkingLot.from_bytes_packed 92 | self.from_bytes_response = carsales_capnp.TotalValue.from_bytes_packed 93 | self.to_bytes = lambda x: x.to_bytes_packed() 94 | else: 95 | self.from_bytes_request = carsales_capnp.ParkingLot.from_bytes 96 | self.from_bytes_response = carsales_capnp.TotalValue.from_bytes 97 | self.to_bytes = lambda x: x.to_bytes() 98 | 99 | def setup(self, request): 100 | result = 0 101 | for car in request.init("cars", rand_int(200)): 102 | random_car(car) 103 | result += calc_value(car) 104 | return result 105 | 106 | def handle(self, request, response): 107 | result = 0 108 | for car in request.cars: 109 | result += calc_value(car) 110 | 111 | response.amount = result 112 | 113 | def check(self, response, expected): 114 | return response.amount == expected 115 | -------------------------------------------------------------------------------- /benchmark/catrank.capnp: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013, Kenton Varda 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright notice, 10 | # this list of conditions and the following disclaimer in the documentation 11 | # and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | using Cxx = import "/capnp/c++.capnp"; 25 | 26 | @0x82beb8e37ff79aba; 27 | $Cxx.namespace("capnp::benchmark::capnp"); 28 | 29 | struct SearchResultList { 30 | results@0: List(SearchResult); 31 | } 32 | 33 | struct SearchResult { 34 | url@0: Text; 35 | score@1: Float64; 36 | snippet@2: Text; 37 | } 38 | -------------------------------------------------------------------------------- /benchmark/catrank.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, Kenton Varda 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright notice, this 8 | // list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation 11 | // and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | syntax = "proto2"; 25 | 26 | package capnp.benchmark.protobuf; 27 | 28 | message SearchResultList { 29 | repeated SearchResult result = 1; 30 | } 31 | 32 | message SearchResult { 33 | optional string url = 1; 34 | optional double score = 2; 35 | optional string snippet = 3; 36 | } 37 | -------------------------------------------------------------------------------- /benchmark/catrank_pb2.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: catrank.proto 3 | 4 | import sys 5 | 6 | _b = sys.version_info[0] < 3 and (lambda x: x) or (lambda x: x.encode("latin1")) 7 | from google.protobuf import descriptor as _descriptor 8 | from google.protobuf import message as _message 9 | from google.protobuf import reflection as _reflection 10 | from google.protobuf import symbol_database as _symbol_database 11 | from google.protobuf import descriptor_pb2 12 | 13 | # @@protoc_insertion_point(imports) 14 | 15 | _sym_db = _symbol_database.Default() 16 | 17 | 18 | DESCRIPTOR = _descriptor.FileDescriptor( 19 | name="catrank.proto", 20 | package="capnp.benchmark.protobuf", 21 | syntax="proto2", 22 | serialized_pb=_b( 23 | '\n\rcatrank.proto\x12\x18\x63\x61pnp.benchmark.protobuf"J\n\x10SearchResultList\x12\x36\n\x06result\x18\x01 \x03(\x0b\x32&.capnp.benchmark.protobuf.SearchResult";\n\x0cSearchResult\x12\x0b\n\x03url\x18\x01 \x01(\t\x12\r\n\x05score\x18\x02 \x01(\x01\x12\x0f\n\x07snippet\x18\x03 \x01(\t' 24 | ), 25 | ) 26 | 27 | 28 | _SEARCHRESULTLIST = _descriptor.Descriptor( 29 | name="SearchResultList", 30 | full_name="capnp.benchmark.protobuf.SearchResultList", 31 | filename=None, 32 | file=DESCRIPTOR, 33 | containing_type=None, 34 | fields=[ 35 | _descriptor.FieldDescriptor( 36 | name="result", 37 | full_name="capnp.benchmark.protobuf.SearchResultList.result", 38 | index=0, 39 | number=1, 40 | type=11, 41 | cpp_type=10, 42 | label=3, 43 | has_default_value=False, 44 | default_value=[], 45 | message_type=None, 46 | enum_type=None, 47 | containing_type=None, 48 | is_extension=False, 49 | extension_scope=None, 50 | options=None, 51 | ), 52 | ], 53 | extensions=[], 54 | nested_types=[], 55 | enum_types=[], 56 | options=None, 57 | is_extendable=False, 58 | syntax="proto2", 59 | extension_ranges=[], 60 | oneofs=[], 61 | serialized_start=43, 62 | serialized_end=117, 63 | ) 64 | 65 | 66 | _SEARCHRESULT = _descriptor.Descriptor( 67 | name="SearchResult", 68 | full_name="capnp.benchmark.protobuf.SearchResult", 69 | filename=None, 70 | file=DESCRIPTOR, 71 | containing_type=None, 72 | fields=[ 73 | _descriptor.FieldDescriptor( 74 | name="url", 75 | full_name="capnp.benchmark.protobuf.SearchResult.url", 76 | index=0, 77 | number=1, 78 | type=9, 79 | cpp_type=9, 80 | label=1, 81 | has_default_value=False, 82 | default_value=_b("").decode("utf-8"), 83 | message_type=None, 84 | enum_type=None, 85 | containing_type=None, 86 | is_extension=False, 87 | extension_scope=None, 88 | options=None, 89 | ), 90 | _descriptor.FieldDescriptor( 91 | name="score", 92 | full_name="capnp.benchmark.protobuf.SearchResult.score", 93 | index=1, 94 | number=2, 95 | type=1, 96 | cpp_type=5, 97 | label=1, 98 | has_default_value=False, 99 | default_value=float(0), 100 | message_type=None, 101 | enum_type=None, 102 | containing_type=None, 103 | is_extension=False, 104 | extension_scope=None, 105 | options=None, 106 | ), 107 | _descriptor.FieldDescriptor( 108 | name="snippet", 109 | full_name="capnp.benchmark.protobuf.SearchResult.snippet", 110 | index=2, 111 | number=3, 112 | type=9, 113 | cpp_type=9, 114 | label=1, 115 | has_default_value=False, 116 | default_value=_b("").decode("utf-8"), 117 | message_type=None, 118 | enum_type=None, 119 | containing_type=None, 120 | is_extension=False, 121 | extension_scope=None, 122 | options=None, 123 | ), 124 | ], 125 | extensions=[], 126 | nested_types=[], 127 | enum_types=[], 128 | options=None, 129 | is_extendable=False, 130 | syntax="proto2", 131 | extension_ranges=[], 132 | oneofs=[], 133 | serialized_start=119, 134 | serialized_end=178, 135 | ) 136 | 137 | _SEARCHRESULTLIST.fields_by_name["result"].message_type = _SEARCHRESULT 138 | DESCRIPTOR.message_types_by_name["SearchResultList"] = _SEARCHRESULTLIST 139 | DESCRIPTOR.message_types_by_name["SearchResult"] = _SEARCHRESULT 140 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 141 | 142 | SearchResultList = _reflection.GeneratedProtocolMessageType( 143 | "SearchResultList", 144 | (_message.Message,), 145 | dict( 146 | DESCRIPTOR=_SEARCHRESULTLIST, 147 | __module__="catrank_pb2", 148 | # @@protoc_insertion_point(class_scope:capnp.benchmark.protobuf.SearchResultList) 149 | ), 150 | ) 151 | _sym_db.RegisterMessage(SearchResultList) 152 | 153 | SearchResult = _reflection.GeneratedProtocolMessageType( 154 | "SearchResult", 155 | (_message.Message,), 156 | dict( 157 | DESCRIPTOR=_SEARCHRESULT, 158 | __module__="catrank_pb2", 159 | # @@protoc_insertion_point(class_scope:capnp.benchmark.protobuf.SearchResult) 160 | ), 161 | ) 162 | _sym_db.RegisterMessage(SearchResult) 163 | 164 | 165 | # @@protoc_insertion_point(module_scope) 166 | -------------------------------------------------------------------------------- /benchmark/catrank_proto.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from common import rand_int, rand_double, rand_bool, WORDS, from_bytes_helper 4 | from random import choice 5 | from string import ascii_letters 6 | 7 | try: 8 | # Python 2 9 | from itertools import izip 10 | except ImportError: 11 | izip = zip 12 | import catrank_pb2 13 | 14 | 15 | class Benchmark: 16 | def __init__(self, compression): 17 | self.Request = catrank_pb2.SearchResultList 18 | self.Response = catrank_pb2.SearchResultList 19 | self.from_bytes_request = from_bytes_helper(catrank_pb2.SearchResultList) 20 | self.from_bytes_response = from_bytes_helper(catrank_pb2.SearchResultList) 21 | self.to_bytes = lambda x: x.SerializeToString() 22 | 23 | def setup(self, request): 24 | goodCount = 0 25 | count = rand_int(1000) 26 | 27 | for i in range(count): 28 | result = request.result.add() 29 | result.score = 1000 - i 30 | url_size = rand_int(100) 31 | result.url = "http://example.com/" + "".join( 32 | [choice(ascii_letters) for _ in range(url_size)] 33 | ) 34 | 35 | isCat = rand_bool() 36 | isDog = rand_bool() 37 | if isCat and not isDog: 38 | goodCount += 1 39 | 40 | snippet = [choice(WORDS) for i in range(rand_int(20))] 41 | 42 | if isCat: 43 | snippet.append(" cat ") 44 | if isDog: 45 | snippet.append(" dog ") 46 | 47 | snippet += [choice(WORDS) for i in range(rand_int(20))] 48 | 49 | result.snippet = "".join(snippet) 50 | 51 | return goodCount 52 | 53 | def handle(self, request, response): 54 | for req in request.result: 55 | resp = response.result.add() 56 | score = req.score 57 | 58 | if " cat " in req.snippet: 59 | score *= 10000 60 | if " dog " in req.snippet: 61 | score /= 10000 62 | 63 | resp.score = score 64 | resp.url = req.url 65 | resp.snippet = req.snippet 66 | 67 | def check(self, response, expected): 68 | goodCount = 0 69 | 70 | for result in response.result: 71 | if result.score > 1001: 72 | goodCount += 1 73 | 74 | return goodCount == expected 75 | -------------------------------------------------------------------------------- /benchmark/catrank_pycapnp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import capnp 4 | import catrank_capnp 5 | from common import rand_int, rand_double, rand_bool, WORDS 6 | from random import choice 7 | from string import ascii_letters 8 | 9 | try: 10 | # Python 2 11 | from itertools import izip 12 | except ImportError: 13 | izip = zip 14 | 15 | 16 | class Benchmark: 17 | def __init__(self, compression): 18 | self.Request = catrank_capnp.SearchResultList.new_message 19 | self.Response = catrank_capnp.SearchResultList.new_message 20 | if compression == "packed": 21 | self.from_bytes_request = catrank_capnp.SearchResultList.from_bytes_packed 22 | self.from_bytes_response = catrank_capnp.SearchResultList.from_bytes_packed 23 | self.to_bytes = lambda x: x.to_bytes_packed() 24 | else: 25 | self.from_bytes_request = catrank_capnp.SearchResultList.from_bytes 26 | self.from_bytes_response = catrank_capnp.SearchResultList.from_bytes 27 | self.to_bytes = lambda x: x.to_bytes() 28 | 29 | def setup(self, request): 30 | goodCount = 0 31 | count = rand_int(1000) 32 | 33 | results = request.init("results", count) 34 | 35 | for i, result in enumerate(results): 36 | result.score = 1000 - i 37 | url_size = rand_int(100) 38 | result.url = "http://example.com/" + "".join( 39 | [choice(ascii_letters) for _ in range(url_size)] 40 | ) 41 | 42 | isCat = rand_bool() 43 | isDog = rand_bool() 44 | if isCat and not isDog: 45 | goodCount += 1 46 | 47 | snippet = [choice(WORDS) for i in range(rand_int(20))] 48 | 49 | if isCat: 50 | snippet.append(" cat ") 51 | if isDog: 52 | snippet.append(" dog ") 53 | 54 | snippet += [choice(WORDS) for i in range(rand_int(20))] 55 | 56 | result.snippet = "".join(snippet) 57 | 58 | return goodCount 59 | 60 | def handle(self, request, response): 61 | results = response.init("results", len(request.results)) 62 | 63 | for req, resp in izip(request.results, results): 64 | score = req.score 65 | 66 | if " cat " in req.snippet: 67 | score *= 10000 68 | if " dog " in req.snippet: 69 | score /= 10000 70 | 71 | resp.score = score 72 | resp.url = req.url 73 | resp.snippet = req.snippet 74 | 75 | def check(self, response, expected): 76 | goodCount = 0 77 | 78 | for result in response.results: 79 | if result.score > 1001: 80 | goodCount += 1 81 | 82 | return goodCount == expected 83 | -------------------------------------------------------------------------------- /benchmark/common.py: -------------------------------------------------------------------------------- 1 | from random import random 2 | import pyximport 3 | 4 | importers = pyximport.install() 5 | from common_fast import rand_int, rand_double, rand_bool 6 | 7 | pyximport.uninstall(*importers) 8 | 9 | WORDS = [ 10 | "foo ", 11 | "bar ", 12 | "baz ", 13 | "qux ", 14 | "quux ", 15 | "corge ", 16 | "grault ", 17 | "garply ", 18 | "waldo ", 19 | "fred ", 20 | "plugh ", 21 | "xyzzy ", 22 | "thud ", 23 | ] 24 | 25 | 26 | def from_bytes_helper(klass): 27 | def helper(text): 28 | obj = klass() 29 | obj.ParseFromString(text) 30 | return obj 31 | 32 | return helper 33 | 34 | 35 | def pass_by_object(reuse, iters, benchmark): 36 | for _ in range(iters): 37 | request = benchmark.Request() 38 | expected = benchmark.setup(request) 39 | 40 | response = benchmark.Response() 41 | benchmark.handle(request, response) 42 | 43 | if not benchmark.check(response, expected): 44 | raise ValueError("Expected {}".format(expected)) 45 | 46 | 47 | def pass_by_bytes(reuse, iters, benchmark): 48 | for _ in range(iters): 49 | request = benchmark.Request() 50 | expected = benchmark.setup(request) 51 | req_bytes = benchmark.to_bytes(request) 52 | 53 | request2 = benchmark.from_bytes_request(req_bytes) 54 | response = benchmark.Response() 55 | benchmark.handle(request2, response) 56 | resp_bytes = benchmark.to_bytes(response) 57 | 58 | response2 = benchmark.from_bytes_response(resp_bytes) 59 | if not benchmark.check(response2, expected): 60 | raise ValueError("Expected {}".format(expected)) 61 | 62 | 63 | def do_benchmark(mode, *args, **kwargs): 64 | if mode == "client": 65 | pass 66 | elif mode == "object": 67 | return pass_by_object(*args, **kwargs) 68 | elif mode == "bytes": 69 | return pass_by_bytes(*args, **kwargs) 70 | else: 71 | raise ValueError("Unknown mode: " + str(mode)) 72 | 73 | 74 | # typedef typename BenchmarkTypes::template BenchmarkMethods 75 | # BenchmarkMethods; 76 | # if (mode == "client") { 77 | # return BenchmarkMethods::syncClient(STDIN_FILENO, STDOUT_FILENO, iters); 78 | # } else if (mode == "server") { 79 | # return BenchmarkMethods::server(STDIN_FILENO, STDOUT_FILENO, iters); 80 | # } else if (mode == "object") { 81 | # return BenchmarkMethods::passByObject(iters, false); 82 | # } else if (mode == "object-size") { 83 | # return BenchmarkMethods::passByObject(iters, true); 84 | # } else if (mode == "bytes") { 85 | # return BenchmarkMethods::passByBytes(iters); 86 | # } else if (mode == "pipe") { 87 | # return passByPipe(BenchmarkMethods::syncClient, iters); 88 | # } else if (mode == "pipe-async") { 89 | # return passByPipe(BenchmarkMethods::asyncClient, iters); 90 | # } else { 91 | # fprintf(stderr, "Unknown mode: %s\n", mode.c_str()); 92 | # exit(1); 93 | # } 94 | # } 95 | -------------------------------------------------------------------------------- /benchmark/common_fast.pyx: -------------------------------------------------------------------------------- 1 | from libc.stdint cimport * 2 | 3 | cdef uint32_t A = 1664525 4 | cdef uint32_t C = 1013904223 5 | cdef uint32_t state = C 6 | cdef int32_t MAX_INT = 2**31 - 1 7 | 8 | cpdef uint32_t nextFastRand(): 9 | global state 10 | state = A * state + C 11 | return state 12 | 13 | cpdef uint32_t rand_int(uint32_t range): 14 | return nextFastRand() % range 15 | 16 | cpdef double rand_double(double range): 17 | return nextFastRand() * range / MAX_INT 18 | 19 | cpdef bint rand_bool(): 20 | return nextFastRand() % 2 21 | -------------------------------------------------------------------------------- /benchmark/eval.capnp: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013, Kenton Varda 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright notice, 10 | # this list of conditions and the following disclaimer in the documentation 11 | # and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | using Cxx = import "/capnp/c++.capnp"; 25 | 26 | @0xe12dc4c3e70e9eda; 27 | $Cxx.namespace("capnp::benchmark::capnp"); 28 | 29 | enum Operation { 30 | add @0; 31 | subtract @1; 32 | multiply @2; 33 | divide @3; 34 | modulus @4; 35 | } 36 | 37 | struct Expression { 38 | op@0: Operation; 39 | 40 | left :union { 41 | value@1: Int32; 42 | expression@2: Expression; 43 | } 44 | 45 | right :union { 46 | value@3: Int32; 47 | expression@4: Expression; 48 | } 49 | } 50 | 51 | struct EvaluationResult { 52 | value@0: Int32; 53 | } 54 | -------------------------------------------------------------------------------- /benchmark/eval.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, Kenton Varda 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright notice, this 8 | // list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation 11 | // and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | syntax = "proto2"; 25 | 26 | package capnp.benchmark.protobuf; 27 | 28 | enum Operation { 29 | ADD = 0; 30 | SUBTRACT = 1; 31 | MULTIPLY = 2; 32 | DIVIDE = 3; 33 | MODULUS = 4; 34 | } 35 | 36 | message Expression { 37 | required Operation op = 1; 38 | 39 | optional int32 left_value = 2; 40 | optional Expression left_expression = 3; 41 | 42 | optional int32 right_value = 4; 43 | optional Expression right_expression = 5; 44 | } 45 | 46 | message EvaluationResult { 47 | required sint32 value = 1; 48 | } 49 | -------------------------------------------------------------------------------- /benchmark/eval_proto.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from common import rand_int, rand_double, rand_bool, from_bytes_helper 4 | from random import choice 5 | import eval_pb2 6 | 7 | MAX_INT = 2**31 - 1 8 | MIN_INT = -(2**31) 9 | 10 | OPERATIONS = ["add", "subtract", "multiply", "divide", "modulus"] 11 | 12 | 13 | def clamp(res): 14 | if res > MAX_INT: 15 | return MAX_INT 16 | elif res < MIN_INT: 17 | return MIN_INT 18 | else: 19 | return res 20 | 21 | 22 | def div(a, b): 23 | if b == 0: 24 | return MAX_INT 25 | if a == MIN_INT and b == -1: 26 | return MAX_INT 27 | 28 | return a // b 29 | 30 | 31 | def mod(a, b): 32 | if b == 0: 33 | return MAX_INT 34 | if a == MIN_INT and b == -1: 35 | return MAX_INT 36 | 37 | return a % b 38 | 39 | 40 | def make_expression(exp, depth): 41 | exp.op = rand_int(len(OPERATIONS)) 42 | 43 | if rand_int(8) < depth: 44 | left = rand_int(128) + 1 45 | exp.left_value = left 46 | else: 47 | left = make_expression(exp.left_expression, depth + 1) 48 | 49 | if rand_int(8) < depth: 50 | right = rand_int(128) + 1 51 | exp.right_value = right 52 | else: 53 | right = make_expression(exp.right_expression, depth + 1) 54 | 55 | op = exp.op 56 | if op == 0: 57 | return clamp(left + right) 58 | elif op == 1: 59 | return clamp(left - right) 60 | elif op == 2: 61 | return clamp(left * right) 62 | elif op == 3: 63 | return div(left, right) 64 | elif op == 4: 65 | return mod(left, right) 66 | raise RuntimeError("op wasn't a valid value: " + str(op)) 67 | 68 | 69 | def evaluate_expression(exp): 70 | left = 0 71 | right = 0 72 | 73 | if exp.HasField("left_value"): 74 | left = exp.left_value 75 | else: 76 | left = evaluate_expression(exp.left_expression) 77 | 78 | if exp.HasField("right_value"): 79 | right = exp.right_value 80 | else: 81 | right = evaluate_expression(exp.right_expression) 82 | 83 | op = exp.op 84 | if op == 0: 85 | return clamp(left + right) 86 | elif op == 1: 87 | return clamp(left - right) 88 | elif op == 2: 89 | return clamp(left * right) 90 | elif op == 3: 91 | return div(left, right) 92 | elif op == 4: 93 | return mod(left, right) 94 | raise RuntimeError("op wasn't a valid value: " + str(op)) 95 | 96 | 97 | class Benchmark: 98 | def __init__(self, compression): 99 | self.Request = eval_pb2.Expression 100 | self.Response = eval_pb2.EvaluationResult 101 | self.from_bytes_request = from_bytes_helper(eval_pb2.Expression) 102 | self.from_bytes_response = from_bytes_helper(eval_pb2.EvaluationResult) 103 | self.to_bytes = lambda x: x.SerializeToString() 104 | 105 | def setup(self, request): 106 | return make_expression(request, 0) 107 | 108 | def handle(self, request, response): 109 | response.value = evaluate_expression(request) 110 | 111 | def check(self, response, expected): 112 | return response.value == expected 113 | -------------------------------------------------------------------------------- /benchmark/eval_pycapnp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import capnp 4 | import eval_capnp 5 | from common import rand_int, rand_double, rand_bool 6 | from random import choice 7 | 8 | MAX_INT = 2**31 - 1 9 | MIN_INT = -(2**31) 10 | 11 | OPERATIONS = ["add", "subtract", "multiply", "divide", "modulus"] 12 | 13 | 14 | def clamp(res): 15 | if res > MAX_INT: 16 | return MAX_INT 17 | elif res < MIN_INT: 18 | return MIN_INT 19 | else: 20 | return res 21 | 22 | 23 | def div(a, b): 24 | if b == 0: 25 | return MAX_INT 26 | if a == MIN_INT and b == -1: 27 | return MAX_INT 28 | 29 | return a // b 30 | 31 | 32 | def mod(a, b): 33 | if b == 0: 34 | return MAX_INT 35 | if a == MIN_INT and b == -1: 36 | return MAX_INT 37 | 38 | return a % b 39 | 40 | 41 | def make_expression(exp, depth): 42 | exp.op = choice(OPERATIONS) 43 | 44 | if rand_int(8) < depth: 45 | left = rand_int(128) + 1 46 | exp.left.value = left 47 | else: 48 | left = make_expression(exp.left.init("expression"), depth + 1) 49 | 50 | if rand_int(8) < depth: 51 | right = rand_int(128) + 1 52 | exp.right.value = right 53 | else: 54 | right = make_expression(exp.right.init("expression"), depth + 1) 55 | 56 | op = exp.op 57 | if op == "add": 58 | return clamp(left + right) 59 | elif op == "subtract": 60 | return clamp(left - right) 61 | elif op == "multiply": 62 | return clamp(left * right) 63 | elif op == "divide": 64 | return div(left, right) 65 | elif op == "modulus": 66 | return mod(left, right) 67 | raise RuntimeError("op wasn't a valid value: " + str(op)) 68 | 69 | 70 | def evaluate_expression(exp): 71 | left = 0 72 | right = 0 73 | 74 | which = exp.left.which() 75 | if which == "value": 76 | left = exp.left.value 77 | elif which == "expression": 78 | left = evaluate_expression(exp.left.expression) 79 | 80 | which = exp.right.which() 81 | if which == "value": 82 | right = exp.right.value 83 | elif which == "expression": 84 | right = evaluate_expression(exp.right.expression) 85 | 86 | op = exp.op 87 | if op == "add": 88 | return clamp(left + right) 89 | elif op == "subtract": 90 | return clamp(left - right) 91 | elif op == "multiply": 92 | return clamp(left * right) 93 | elif op == "divide": 94 | return div(left, right) 95 | elif op == "modulus": 96 | return mod(left, right) 97 | raise RuntimeError("op wasn't a valid value: " + str(op)) 98 | 99 | 100 | class Benchmark: 101 | def __init__(self, compression): 102 | self.Request = eval_capnp.Expression.new_message 103 | self.Response = eval_capnp.EvaluationResult.new_message 104 | if compression == "packed": 105 | self.from_bytes_request = eval_capnp.Expression.from_bytes_packed 106 | self.from_bytes_response = eval_capnp.EvaluationResult.from_bytes_packed 107 | self.to_bytes = lambda x: x.to_bytes_packed() 108 | else: 109 | self.from_bytes_request = eval_capnp.Expression.from_bytes 110 | self.from_bytes_response = eval_capnp.EvaluationResult.from_bytes 111 | self.to_bytes = lambda x: x.to_bytes() 112 | 113 | def setup(self, request): 114 | return make_expression(request, 0) 115 | 116 | def handle(self, request, response): 117 | response.value = evaluate_expression(request) 118 | 119 | def check(self, response, expected): 120 | return response.value == expected 121 | -------------------------------------------------------------------------------- /buildutils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capnproto/pycapnp/a89eb0dee6c67049bf2f6b875e51dd3f71a6abd1/buildutils/__init__.py -------------------------------------------------------------------------------- /buildutils/build.py: -------------------------------------------------------------------------------- 1 | "Build the bundled capnp distribution" 2 | 3 | import subprocess 4 | import os 5 | import shutil 6 | import struct 7 | import sys 8 | 9 | 10 | def build_libcapnp(bundle_dir, build_dir): # noqa: C901 11 | """ 12 | Build capnproto 13 | """ 14 | bundle_dir = os.path.abspath(bundle_dir) 15 | capnp_dir = os.path.join(bundle_dir, "capnproto-c++") 16 | build_dir = os.path.abspath(build_dir) 17 | tmp_dir = os.path.join(capnp_dir, "build{}".format(8 * struct.calcsize("P"))) 18 | 19 | # Clean the tmp build directory every time 20 | if os.path.exists(tmp_dir): 21 | shutil.rmtree(tmp_dir) 22 | os.mkdir(tmp_dir) 23 | 24 | cxxflags = os.environ.get("CXXFLAGS", None) 25 | ldflags = os.environ.get("LDFLAGS", None) 26 | os.environ["CXXFLAGS"] = (cxxflags or "") + " -O2 -DNDEBUG" 27 | os.environ["LDFLAGS"] = ldflags or "" 28 | 29 | # Enable ninja for compilation if available 30 | build_type = [] 31 | if shutil.which("ninja") and os.name != "nt": 32 | build_type = ["-G", "Ninja"] 33 | 34 | # Determine python shell architecture for Windows 35 | python_arch = 8 * struct.calcsize("P") 36 | build_arch = [] 37 | build_flags = [] 38 | if os.name == "nt": 39 | if python_arch == 64: 40 | build_arch_flag = "x64" 41 | elif python_arch == 32: 42 | build_arch_flag = "Win32" 43 | else: 44 | raise RuntimeError("Unknown windows build arch") 45 | build_arch = ["-A", build_arch_flag] 46 | build_flags = ["--config", "Release"] 47 | print("Building module for {}".format(python_arch)) 48 | 49 | if not shutil.which("cmake"): 50 | raise RuntimeError("Could not find cmake in your path!") 51 | 52 | args = [ 53 | "cmake", 54 | "-DCMAKE_POSITION_INDEPENDENT_CODE=1", 55 | "-DBUILD_TESTING=OFF", 56 | "-DBUILD_SHARED_LIBS=OFF", 57 | "-DCMAKE_INSTALL_PREFIX:PATH={}".format(build_dir), 58 | capnp_dir, 59 | ] 60 | args.extend(build_type) 61 | args.extend(build_arch) 62 | conf = subprocess.Popen(args, cwd=tmp_dir, stdout=sys.stdout) 63 | returncode = conf.wait() 64 | if returncode != 0: 65 | raise RuntimeError("CMake failed {}".format(returncode)) 66 | 67 | # Run build through cmake 68 | args = [ 69 | "cmake", 70 | "--build", 71 | ".", 72 | "--target", 73 | "install", 74 | ] 75 | args.extend(build_flags) 76 | build = subprocess.Popen(args, cwd=tmp_dir, stdout=sys.stdout) 77 | returncode = build.wait() 78 | if cxxflags is None: 79 | del os.environ["CXXFLAGS"] 80 | else: 81 | os.environ["CXXFLAGS"] = cxxflags 82 | if ldflags is None: 83 | del os.environ["LDFLAGS"] 84 | else: 85 | os.environ["LDFLAGS"] = ldflags 86 | if returncode != 0: 87 | raise RuntimeError("capnproto compilation failed: {}".format(returncode)) 88 | -------------------------------------------------------------------------------- /buildutils/bundle.py: -------------------------------------------------------------------------------- 1 | """utilities for fetching build dependencies.""" 2 | 3 | # 4 | # Copyright (C) PyZMQ Developers 5 | # Distributed under the terms of the Modified BSD License. 6 | # 7 | # This bundling code is largely adapted from pyzmq-static's get.sh by 8 | # Brandon Craig-Rhodes, which is itself BSD licensed. 9 | # 10 | # Adapted for use in pycapnp from pyzmq. See https://github.com/zeromq/pyzmq 11 | # for original project. 12 | 13 | 14 | import fileinput # noqa 15 | import os 16 | import shutil 17 | import tarfile 18 | 19 | from urllib.request import urlopen 20 | 21 | pjoin = os.path.join 22 | 23 | 24 | # 25 | # Constants 26 | # 27 | 28 | 29 | bundled_version = (1, 0, 1) 30 | libcapnp_name = "capnproto-c++-%i.%i.%i.tar.gz" % (bundled_version) 31 | libcapnp_url = "https://capnproto.org/" + libcapnp_name 32 | 33 | HERE = os.path.dirname(__file__) 34 | ROOT = os.path.dirname(HERE) 35 | 36 | 37 | # 38 | # Utilities 39 | # 40 | 41 | 42 | def untgz(archive): 43 | """Remove .tar.gz""" 44 | return archive.replace(".tar.gz", "") 45 | 46 | 47 | def localpath(*args): 48 | """construct an absolute path from a list relative to the root pycapnp directory""" 49 | plist = [ROOT] + list(args) 50 | return os.path.abspath(pjoin(*plist)) 51 | 52 | 53 | def fetch_archive(savedir, url, force=False): 54 | """download an archive to a specific location""" 55 | req = urlopen(url) 56 | # Lookup filename 57 | fname = req.info().get_filename() 58 | if not fname: 59 | fname = os.path.basename(url) 60 | dest = pjoin(savedir, fname) 61 | if os.path.exists(dest) and not force: 62 | print("already have %s" % fname) 63 | return dest 64 | print("fetching %s into %s" % (url, savedir)) 65 | if not os.path.exists(savedir): 66 | os.makedirs(savedir) 67 | with open(dest, "wb") as f: 68 | f.write(req.read()) 69 | return dest 70 | 71 | 72 | # 73 | # libcapnp 74 | # 75 | 76 | 77 | def fetch_libcapnp(savedir, url=None): 78 | """download and extract libcapnp""" 79 | is_preconfigured = False 80 | if url is None: 81 | url = libcapnp_url 82 | is_preconfigured = True 83 | dest = pjoin(savedir, "capnproto-c++") 84 | if os.path.exists(dest): 85 | print("already have %s" % dest) 86 | return 87 | fname = fetch_archive(savedir, url) 88 | tf = tarfile.open(fname) 89 | with_version = pjoin(savedir, tf.firstmember.path) 90 | tf.extractall(savedir) 91 | tf.close() 92 | # remove version suffix: 93 | if is_preconfigured: 94 | shutil.move(with_version, dest) 95 | else: 96 | cpp_dir = os.path.join(with_version, "c++") 97 | shutil.move(cpp_dir, dest) 98 | -------------------------------------------------------------------------------- /capnp/__init__.pxd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capnproto/pycapnp/a89eb0dee6c67049bf2f6b875e51dd3f71a6abd1/capnp/__init__.pxd -------------------------------------------------------------------------------- /capnp/__init__.py: -------------------------------------------------------------------------------- 1 | """A python library wrapping the Cap'n Proto C++ library 2 | 3 | Example Usage:: 4 | 5 | import capnp 6 | 7 | addressbook = capnp.load('addressbook.capnp') 8 | 9 | # Building 10 | addresses = addressbook.AddressBook.newMessage() 11 | people = addresses.init('people', 1) 12 | 13 | alice = people[0] 14 | alice.id = 123 15 | alice.name = 'Alice' 16 | alice.email = 'alice@example.com' 17 | alicePhone = alice.init('phones', 1)[0] 18 | alicePhone.type = 'mobile' 19 | 20 | f = open('example.bin', 'w') 21 | addresses.write(f) 22 | f.close() 23 | 24 | # Reading 25 | f = open('example.bin') 26 | 27 | addresses = addressbook.AddressBook.read(f) 28 | 29 | for person in addresses.people: 30 | print(person.name, ':', person.email) 31 | for phone in person.phones: 32 | print(phone.type, ':', phone.number) 33 | """ 34 | 35 | # flake8: noqa F401 F403 F405 36 | from .version import version as __version__ 37 | from .lib.capnp import * 38 | from .lib.capnp import ( 39 | _CapabilityClient, 40 | _DynamicCapabilityClient, 41 | _DynamicListBuilder, 42 | _DynamicListReader, 43 | _DynamicOrphan, 44 | _DynamicResizableListBuilder, 45 | _DynamicStructBuilder, 46 | _DynamicStructReader, 47 | _EventLoop, 48 | _InterfaceModule, 49 | _ListSchema, 50 | _MallocMessageBuilder, 51 | _PackedFdMessageReader, 52 | _StreamFdMessageReader, 53 | _StructModule, 54 | _write_message_to_fd, 55 | _write_packed_message_to_fd, 56 | _AsyncIoStream as AsyncIoStream, 57 | _init_capnp_api, 58 | ) 59 | 60 | _init_capnp_api() 61 | add_import_hook() # enable import hook by default 62 | -------------------------------------------------------------------------------- /capnp/_gen.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from jinja2 import Environment, PackageLoader 5 | 6 | import capnp 7 | import schema_capnp 8 | 9 | 10 | def find_type(code, id): 11 | for node in code["nodes"]: 12 | if node["id"] == id: 13 | return node 14 | 15 | return None 16 | 17 | 18 | def main(): 19 | env = Environment(loader=PackageLoader("capnp", "templates")) 20 | env.filters["format_name"] = lambda name: name[name.find(":") + 1 :] 21 | 22 | code = schema_capnp.CodeGeneratorRequest.read(sys.stdin) 23 | code = code.to_dict() 24 | code["nodes"] = [ 25 | node for node in code["nodes"] if "struct" in node and node["scopeId"] != 0 26 | ] 27 | for node in code["nodes"]: 28 | displayName = node["displayName"] 29 | parent, path = displayName.split(":") 30 | node["module_path"] = ( 31 | parent.replace(".", "_") 32 | + "." 33 | + ".".join([x[0].upper() + x[1:] for x in path.split(".")]) 34 | ) 35 | node["module_name"] = path.replace(".", "_") 36 | node["c_module_path"] = "::".join( 37 | [x[0].upper() + x[1:] for x in path.split(".")] 38 | ) 39 | node["schema"] = "_{}_Schema".format(node["module_name"]) 40 | is_union = False 41 | for field in node["struct"]["fields"]: 42 | if field["discriminantValue"] != 65535: 43 | is_union = True 44 | field["c_name"] = field["name"][0].upper() + field["name"][1:] 45 | if "slot" in field: 46 | field["type"] = list(field["slot"]["type"].keys())[0] 47 | if not isinstance(field["slot"]["type"][field["type"]], dict): 48 | continue 49 | sub_type = field["slot"]["type"][field["type"]].get("typeId", None) 50 | if sub_type: 51 | field["sub_type"] = find_type(code, sub_type) 52 | sub_type = field["slot"]["type"][field["type"]].get("elementType", None) 53 | if sub_type: 54 | field["sub_type"] = sub_type 55 | else: 56 | field["type"] = find_type(code, field["group"]["typeId"]) 57 | node["is_union"] = is_union 58 | 59 | include_dir = os.path.abspath(os.path.join(os.path.dirname(capnp.__file__), "..")) 60 | module = env.get_template("module.pyx") 61 | 62 | for f in code["requestedFiles"]: 63 | filename = f["filename"].replace(".", "_") + "_cython.pyx" 64 | 65 | file_code = dict(code) 66 | file_code["nodes"] = [ 67 | node 68 | for node in file_code["nodes"] 69 | if node["displayName"].startswith(f["filename"]) 70 | ] 71 | with open(filename, "w") as out: 72 | out.write(module.render(code=file_code, file=f, include_dir=include_dir)) 73 | 74 | setup = env.get_template("setup.py.tmpl") 75 | with open("setup_capnp.py", "w") as out: 76 | out.write(setup.render(code=code)) 77 | print( 78 | "You now need to build the cython module by running `python setup_capnp.py build_ext --inplace`." 79 | ) 80 | print() 81 | -------------------------------------------------------------------------------- /capnp/helpers/__init__.pxd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capnproto/pycapnp/a89eb0dee6c67049bf2f6b875e51dd3f71a6abd1/capnp/helpers/__init__.pxd -------------------------------------------------------------------------------- /capnp/helpers/capabilityHelper.cpp: -------------------------------------------------------------------------------- 1 | #include "capnp/helpers/capabilityHelper.h" 2 | #include "capnp/lib/capnp_api.h" 3 | 4 | ::kj::Promise> convert_to_pypromise(capnp::RemotePromise promise) { 5 | return promise.then([](capnp::Response&& response) { 6 | return stealPyRef(wrap_dynamic_struct_reader(response)); } ); 7 | } 8 | 9 | void c_reraise_kj_exception() { 10 | GILAcquire gil; 11 | try { 12 | if (PyErr_Occurred()) 13 | ; // let the latest Python exn pass through and ignore the current one 14 | else 15 | throw; 16 | } 17 | catch (kj::Exception& exn) { 18 | auto obj = wrap_kj_exception_for_reraise(exn); 19 | PyErr_SetObject((PyObject*)obj->ob_type, obj); 20 | Py_DECREF(obj); 21 | } 22 | catch (const std::exception& exn) { 23 | PyErr_SetString(PyExc_RuntimeError, exn.what()); 24 | } 25 | catch (...) 26 | { 27 | PyErr_SetString(PyExc_RuntimeError, "Unknown exception"); 28 | } 29 | } 30 | 31 | void check_py_error() { 32 | GILAcquire gil; 33 | PyObject * err = PyErr_Occurred(); 34 | if(err) { 35 | PyObject * ptype, *pvalue, *ptraceback; 36 | PyErr_Fetch(&ptype, &pvalue, &ptraceback); 37 | if(ptype == NULL || pvalue == NULL || ptraceback == NULL) 38 | throw kj::Exception(kj::Exception::Type::FAILED, kj::heapString("capabilityHelper.h"), 44, kj::heapString("Unknown error occurred")); 39 | 40 | PyObject * info = get_exception_info(ptype, pvalue, ptraceback); 41 | 42 | PyObject * py_filename = PyTuple_GetItem(info, 0); 43 | kj::String filename(kj::heapString(PyBytes_AsString(py_filename))); 44 | 45 | PyObject * py_line = PyTuple_GetItem(info, 1); 46 | int line = PyLong_AsLong(py_line); 47 | 48 | PyObject * py_description = PyTuple_GetItem(info, 2); 49 | kj::String description(kj::heapString(PyBytes_AsString(py_description))); 50 | 51 | Py_DECREF(ptype); 52 | Py_DECREF(pvalue); 53 | Py_DECREF(ptraceback); 54 | Py_DECREF(info); 55 | PyErr_Clear(); 56 | 57 | throw kj::Exception(kj::Exception::Type::FAILED, kj::mv(filename), line, kj::mv(description)); 58 | } 59 | } 60 | 61 | kj::Promise> wrapPyFunc(kj::Own func, kj::Own arg) { 62 | GILAcquire gil; 63 | PyObject * result = PyObject_CallFunctionObjArgs(func->obj, arg->obj, NULL); 64 | check_py_error(); 65 | return stealPyRef(result); 66 | } 67 | 68 | ::kj::Promise> then(kj::Promise> promise, 69 | kj::Own func, kj::Own error_func) { 70 | if(error_func->obj == Py_None) 71 | return promise.then([func=kj::mv(func)](kj::Own arg) mutable { 72 | return wrapPyFunc(kj::mv(func), kj::mv(arg)); } ); 73 | else 74 | return promise.then 75 | ([func=kj::mv(func)](kj::Own arg) mutable { 76 | return wrapPyFunc(kj::mv(func), kj::mv(arg)); }, 77 | [error_func=kj::mv(error_func)](kj::Exception arg) mutable { 78 | return wrapPyFunc(kj::mv(error_func), stealPyRef(wrap_kj_exception(arg))); } ); 79 | } 80 | 81 | kj::Promise PythonInterfaceDynamicImpl::call(capnp::InterfaceSchema::Method method, 82 | capnp::CallContext< capnp::DynamicStruct, 83 | capnp::DynamicStruct> context) { 84 | auto methodName = method.getProto().getName(); 85 | 86 | kj::Promise * promise = call_server_method(this->py_server->obj, 87 | const_cast(methodName.cStr()), 88 | context, 89 | this->kj_loop->obj); 90 | 91 | check_py_error(); 92 | 93 | if(promise == nullptr) 94 | return kj::READY_NOW; 95 | 96 | kj::Promise ret(kj::mv(*promise)); 97 | delete promise; 98 | return ret; 99 | }; 100 | 101 | 102 | class ReadPromiseAdapter { 103 | public: 104 | ReadPromiseAdapter(kj::PromiseFulfiller& fulfiller, PyObject* protocol, 105 | void* buffer, size_t minBytes, size_t maxBytes) 106 | : protocol(protocol) { 107 | _asyncio_stream_read_start(protocol, buffer, minBytes, maxBytes, fulfiller); 108 | } 109 | 110 | ~ReadPromiseAdapter() { 111 | _asyncio_stream_read_stop(protocol); 112 | } 113 | 114 | private: 115 | PyObject* protocol; 116 | }; 117 | 118 | 119 | class WritePromiseAdapter { 120 | public: 121 | WritePromiseAdapter(kj::PromiseFulfiller& fulfiller, PyObject* protocol, 122 | kj::ArrayPtr> pieces) 123 | : protocol(protocol) { 124 | _asyncio_stream_write_start(protocol, pieces, fulfiller); 125 | } 126 | 127 | ~WritePromiseAdapter() { 128 | _asyncio_stream_write_stop(protocol); 129 | } 130 | 131 | private: 132 | PyObject* protocol; 133 | 134 | }; 135 | 136 | PyAsyncIoStream::~PyAsyncIoStream() { 137 | _asyncio_stream_close(protocol->obj); 138 | } 139 | 140 | kj::Promise PyAsyncIoStream::tryRead(void* buffer, size_t minBytes, size_t maxBytes) { 141 | return kj::newAdaptedPromise(protocol->obj, buffer, minBytes, maxBytes); 142 | } 143 | 144 | kj::Promise PyAsyncIoStream::write(const void* buffer, size_t size) { 145 | KJ_UNIMPLEMENTED("No use-case AsyncIoStream::write was found yet."); 146 | } 147 | 148 | kj::Promise PyAsyncIoStream::write(kj::ArrayPtr> pieces) { 149 | return kj::newAdaptedPromise(protocol->obj, pieces); 150 | } 151 | 152 | kj::Promise PyAsyncIoStream::whenWriteDisconnected() { 153 | // TODO: Possibly connect this to protocol.connection_lost? 154 | return kj::NEVER_DONE; 155 | } 156 | 157 | void PyAsyncIoStream::shutdownWrite() { 158 | _asyncio_stream_shutdown_write(protocol->obj); 159 | } 160 | 161 | class TaskToPromiseAdapter { 162 | public: 163 | TaskToPromiseAdapter(kj::PromiseFulfiller& fulfiller, 164 | kj::Own task, PyObject* callback) 165 | : task(kj::mv(task)) { 166 | promise_task_add_done_callback(this->task->obj, callback, fulfiller); 167 | } 168 | 169 | ~TaskToPromiseAdapter() { 170 | promise_task_cancel(this->task->obj); 171 | } 172 | 173 | private: 174 | kj::Own task; 175 | }; 176 | 177 | kj::Promise taskToPromise(kj::Own task, PyObject* callback) { 178 | return kj::newAdaptedPromise(kj::mv(task), callback); 179 | } 180 | 181 | ::kj::Promise> tryReadMessage(kj::AsyncIoStream& stream, capnp::ReaderOptions opts) { 182 | return capnp::tryReadMessage(stream, opts) 183 | .then([](kj::Maybe> maybeReader) -> kj::Promise> { 184 | KJ_IF_MAYBE(reader, maybeReader) { 185 | PyObject* pyreader = make_async_message_reader(kj::mv(*reader)); 186 | check_py_error(); 187 | return kj::heap(pyreader); 188 | } else { 189 | return kj::heap(Py_None); 190 | } 191 | }); 192 | } 193 | 194 | void init_capnp_api() { 195 | import_capnp__lib__capnp(); 196 | } 197 | -------------------------------------------------------------------------------- /capnp/helpers/capabilityHelper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "capnp/dynamic.h" 4 | #include 5 | #include 6 | #include 7 | #include "Python.h" 8 | 9 | class GILAcquire { 10 | public: 11 | GILAcquire() : gstate(PyGILState_Ensure()) {} 12 | ~GILAcquire() { 13 | PyGILState_Release(gstate); 14 | } 15 | 16 | PyGILState_STATE gstate; 17 | }; 18 | 19 | class GILRelease { 20 | public: 21 | GILRelease() { 22 | Py_UNBLOCK_THREADS 23 | } 24 | ~GILRelease() { 25 | Py_BLOCK_THREADS 26 | } 27 | 28 | PyThreadState *_save; // The macros above read/write from this variable 29 | }; 30 | 31 | class PyRefCounter { 32 | public: 33 | PyObject * obj; 34 | 35 | PyRefCounter(PyObject * o) : obj(o) { 36 | GILAcquire gil; 37 | Py_INCREF(obj); 38 | } 39 | 40 | PyRefCounter(const PyRefCounter & ref) : obj(ref.obj) { 41 | GILAcquire gil; 42 | Py_INCREF(obj); 43 | } 44 | 45 | ~PyRefCounter() { 46 | GILAcquire gil; 47 | Py_DECREF(obj); 48 | } 49 | }; 50 | 51 | inline kj::Own stealPyRef(PyObject* o) { 52 | auto ret = kj::heap(o); 53 | Py_DECREF(o); 54 | return ret; 55 | } 56 | 57 | ::kj::Promise> convert_to_pypromise(capnp::RemotePromise promise); 58 | 59 | inline ::kj::Promise> convert_to_pypromise(kj::Promise promise) { 60 | return promise.then([]() { 61 | GILAcquire gil; 62 | return kj::heap(Py_None); 63 | }); 64 | } 65 | 66 | void c_reraise_kj_exception(); 67 | 68 | void check_py_error(); 69 | 70 | ::kj::Promise> then(kj::Promise> promise, 71 | kj::Own func, kj::Own error_func); 72 | 73 | class PythonInterfaceDynamicImpl final: public capnp::DynamicCapability::Server { 74 | public: 75 | kj::Own py_server; 76 | kj::Own kj_loop; 77 | 78 | #if (CAPNP_VERSION_MAJOR < 1) 79 | PythonInterfaceDynamicImpl(capnp::InterfaceSchema & schema, 80 | kj::Own _py_server, 81 | kj::Own kj_loop) 82 | : capnp::DynamicCapability::Server(schema), 83 | py_server(kj::mv(_py_server)), kj_loop(kj::mv(kj_loop)) { } 84 | #else 85 | PythonInterfaceDynamicImpl(capnp::InterfaceSchema & schema, 86 | kj::Own _py_server, 87 | kj::Own kj_loop) 88 | : capnp::DynamicCapability::Server(schema, { true }), 89 | py_server(kj::mv(_py_server)), kj_loop(kj::mv(kj_loop)) { } 90 | #endif 91 | 92 | ~PythonInterfaceDynamicImpl() { 93 | } 94 | 95 | kj::Promise call(capnp::InterfaceSchema::Method method, 96 | capnp::CallContext< capnp::DynamicStruct, capnp::DynamicStruct> context); 97 | }; 98 | 99 | inline void allowCancellation(capnp::CallContext context) { 100 | #if (CAPNP_VERSION_MAJOR < 1) 101 | context.allowCancellation(); 102 | #endif 103 | } 104 | 105 | class PyAsyncIoStream: public kj::AsyncIoStream { 106 | public: 107 | kj::Own protocol; 108 | 109 | PyAsyncIoStream(kj::Own protocol) : protocol(kj::mv(protocol)) {} 110 | ~PyAsyncIoStream(); 111 | 112 | kj::Promise tryRead(void* buffer, size_t minBytes, size_t maxBytes); 113 | 114 | kj::Promise write(const void* buffer, size_t size); 115 | 116 | kj::Promise write(kj::ArrayPtr> pieces); 117 | 118 | kj::Promise whenWriteDisconnected(); 119 | 120 | void shutdownWrite(); 121 | }; 122 | 123 | template 124 | inline void rejectDisconnected(kj::PromiseFulfiller& fulfiller, kj::StringPtr message) { 125 | fulfiller.reject(KJ_EXCEPTION(DISCONNECTED, message)); 126 | } 127 | inline void rejectVoidDisconnected(kj::PromiseFulfiller& fulfiller, kj::StringPtr message) { 128 | fulfiller.reject(KJ_EXCEPTION(DISCONNECTED, message)); 129 | } 130 | 131 | inline kj::Exception makeException(kj::StringPtr message) { 132 | return KJ_EXCEPTION(FAILED, message); 133 | } 134 | 135 | kj::Promise taskToPromise(kj::Own coroutine, PyObject* callback); 136 | 137 | ::kj::Promise> tryReadMessage(kj::AsyncIoStream& stream, capnp::ReaderOptions opts); 138 | 139 | void init_capnp_api(); 140 | -------------------------------------------------------------------------------- /capnp/helpers/checkCompiler.h: -------------------------------------------------------------------------------- 1 | #ifdef _MSC_VER 2 | #pragma comment(lib, "Ws2_32.lib") 3 | #pragma comment(lib, "advapi32.lib") 4 | #endif 5 | 6 | #include "capnp/dynamic.h" 7 | 8 | static_assert(CAPNP_VERSION >= 8000, "Version of Cap'n Proto C++ Library is too old. Please upgrade to a version >= 0.8 and then re-install this python library"); 9 | -------------------------------------------------------------------------------- /capnp/helpers/deserialize.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "capnp/dynamic.h" 4 | #include "capnp/schema.capnp.h" 5 | 6 | /// @brief Convert the dynamic struct to a Node::Reader 7 | ::capnp::schema::Node::Reader toReader(capnp::DynamicStruct::Reader reader) 8 | { 9 | // requires an intermediate step to AnyStruct before going directly to Node::Reader, 10 | // since there exists no direct conversion from DynamicStruct::Reader to Node::Reader. 11 | return reader.as().as(); 12 | } 13 | -------------------------------------------------------------------------------- /capnp/helpers/fixMaybe.h: -------------------------------------------------------------------------------- 1 | #include "kj/common.h" 2 | #include 3 | 4 | template 5 | T fixMaybe(::kj::Maybe val) { 6 | KJ_IF_MAYBE(new_val, val) { 7 | return *new_val; 8 | } else { 9 | throw std::invalid_argument("Member was null."); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /capnp/helpers/helpers.pxd: -------------------------------------------------------------------------------- 1 | from capnp.includes.capnp_cpp cimport ( 2 | Maybe, PyPromise, VoidPromise, RemotePromise, 3 | DynamicCapability, InterfaceSchema, EnumSchema, StructSchema, DynamicValue, Capability, 4 | RpcSystem, MessageBuilder, Own, PyRefCounter, Node, DynamicStruct, CallContext 5 | ) 6 | 7 | from capnp.includes.schema_cpp cimport ByteArray 8 | 9 | from non_circular cimport c_reraise_kj_exception as reraise_kj_exception 10 | 11 | from cpython.ref cimport PyObject 12 | 13 | cdef extern from "capnp/helpers/fixMaybe.h": 14 | EnumSchema.Enumerant fixMaybe(Maybe[EnumSchema.Enumerant]) except +reraise_kj_exception 15 | StructSchema.Field fixMaybe(Maybe[StructSchema.Field]) except +reraise_kj_exception 16 | 17 | cdef extern from "capnp/helpers/capabilityHelper.h": 18 | PyPromise then(PyPromise promise, Own[PyRefCounter] func, Own[PyRefCounter] error_func) 19 | PyPromise convert_to_pypromise(RemotePromise) 20 | PyPromise convert_to_pypromise(VoidPromise) 21 | VoidPromise taskToPromise(Own[PyRefCounter] coroutine, PyObject* callback) 22 | void allowCancellation(CallContext context) except +reraise_kj_exception nogil 23 | void init_capnp_api() 24 | 25 | cdef extern from "capnp/helpers/rpcHelper.h": 26 | Own[Capability.Client] bootstrapHelper(RpcSystem&) except +reraise_kj_exception 27 | Own[Capability.Client] bootstrapHelperServer(RpcSystem&) except +reraise_kj_exception 28 | 29 | cdef extern from "capnp/helpers/serialize.h": 30 | ByteArray messageToPackedBytes(MessageBuilder &, size_t wordCount) except +reraise_kj_exception 31 | 32 | cdef extern from "capnp/helpers/deserialize.h": 33 | Node.Reader toReader(DynamicStruct.Reader reader) except +reraise_kj_exception 34 | 35 | -------------------------------------------------------------------------------- /capnp/helpers/non_circular.pxd: -------------------------------------------------------------------------------- 1 | from cpython.ref cimport PyObject 2 | from libcpp cimport bool 3 | 4 | cdef extern from "capnp/helpers/capabilityHelper.h": 5 | void c_reraise_kj_exception() 6 | cdef cppclass PyRefCounter: 7 | PyRefCounter(PyObject *) 8 | PyObject * obj 9 | -------------------------------------------------------------------------------- /capnp/helpers/rpcHelper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "capnp/dynamic.h" 4 | #include 5 | #include "capnp/rpc-twoparty.h" 6 | #include "Python.h" 7 | #include "capabilityHelper.h" 8 | 9 | kj::Own bootstrapHelper(capnp::RpcSystem& client) { 10 | capnp::MallocMessageBuilder hostIdMessage(8); 11 | auto hostId = hostIdMessage.initRoot(); 12 | hostId.setSide(capnp::rpc::twoparty::Side::SERVER); 13 | return kj::heap(client.bootstrap(hostId)); 14 | } 15 | 16 | kj::Own bootstrapHelperServer(capnp::RpcSystem& client) { 17 | capnp::MallocMessageBuilder hostIdMessage(8); 18 | auto hostId = hostIdMessage.initRoot(); 19 | hostId.setSide(capnp::rpc::twoparty::Side::CLIENT); 20 | return kj::heap(client.bootstrap(hostId)); 21 | } 22 | -------------------------------------------------------------------------------- /capnp/helpers/serialize.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kj/io.h" 4 | #include "capnp/dynamic.h" 5 | #include "capnp/serialize-packed.h" 6 | 7 | kj::Array< ::capnp::byte> messageToPackedBytes(capnp::MessageBuilder & message, size_t wordCount) 8 | { 9 | 10 | kj::Array result = kj::heapArray(wordCount * 8); 11 | kj::ArrayOutputStream out(result.asPtr()); 12 | capnp::writePackedMessage(out, message); 13 | return heapArray(out.getArray()); // TODO: make this non-copying somehow 14 | } 15 | -------------------------------------------------------------------------------- /capnp/includes/__init__.pxd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capnproto/pycapnp/a89eb0dee6c67049bf2f6b875e51dd3f71a6abd1/capnp/includes/__init__.pxd -------------------------------------------------------------------------------- /capnp/includes/types.pxd: -------------------------------------------------------------------------------- 1 | from cpython.ref cimport PyObject, Py_INCREF, Py_DECREF 2 | from libc.stdint cimport * 3 | ctypedef unsigned int uint 4 | ctypedef uint8_t byte 5 | ctypedef uint8_t UInt8 6 | ctypedef uint16_t UInt16 7 | ctypedef uint32_t UInt32 8 | ctypedef uint64_t UInt64 9 | ctypedef int8_t Int8 10 | ctypedef int16_t Int16 11 | ctypedef int32_t Int32 12 | ctypedef int64_t Int64 13 | 14 | ctypedef char * Object 15 | ctypedef bint Bool 16 | ctypedef float Float32 17 | ctypedef double Float64 18 | from libcpp cimport bool as cbool 19 | -------------------------------------------------------------------------------- /capnp/lib/__init__.pxd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capnproto/pycapnp/a89eb0dee6c67049bf2f6b875e51dd3f71a6abd1/capnp/lib/__init__.pxd -------------------------------------------------------------------------------- /capnp/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capnproto/pycapnp/a89eb0dee6c67049bf2f6b875e51dd3f71a6abd1/capnp/lib/__init__.py -------------------------------------------------------------------------------- /capnp/lib/capnp.pxd: -------------------------------------------------------------------------------- 1 | # cython: language_level = 2 2 | 3 | from capnp.includes cimport capnp_cpp as capnp 4 | from capnp.includes cimport schema_cpp 5 | from capnp.includes.capnp_cpp cimport ( 6 | Schema as C_Schema, StructSchema as C_StructSchema, InterfaceSchema as C_InterfaceSchema, 7 | EnumSchema as C_EnumSchema, ListSchema as C_ListSchema, DynamicStruct as C_DynamicStruct, 8 | DynamicValue as C_DynamicValue, Type as C_Type, DynamicList as C_DynamicList, SchemaLoader as C_SchemaLoader, 9 | SchemaParser as C_SchemaParser, ParsedSchema as C_ParsedSchema, VOID, ArrayPtr, StringPtr, 10 | String, StringTree, DynamicOrphan as C_DynamicOrphan, AnyPointer as C_DynamicObject, 11 | DynamicCapability as C_DynamicCapability, Request, Response, RemotePromise, Promise, 12 | CallContext, RpcSystem, makeRpcServer, makeRpcClient, Capability as C_Capability, 13 | TwoPartyVatNetwork as C_TwoPartyVatNetwork, Side, AsyncIoStream, Own, 14 | DynamicStruct_Builder, PyRefCounter, PyAsyncIoStream 15 | ) 16 | from capnp.includes.schema_cpp cimport Node as C_Node, EnumNode as C_EnumNode 17 | from capnp.includes.types cimport * 18 | from capnp.helpers cimport helpers 19 | 20 | cdef void reraise_kj_exception() 21 | 22 | cdef class _StructSchemaField: 23 | cdef C_StructSchema.Field thisptr 24 | cdef object _parent 25 | cdef _init(self, C_StructSchema.Field other, parent=?) 26 | 27 | cdef class _StringArrayPtr: 28 | cdef StringPtr * thisptr 29 | cdef object parent 30 | cdef size_t size 31 | cdef ArrayPtr[StringPtr] asArrayPtr(self) 32 | 33 | cdef class SchemaLoader: 34 | cdef C_SchemaLoader * thisptr 35 | 36 | cdef class SchemaParser: 37 | cdef C_SchemaParser * thisptr 38 | cdef public dict modules_by_id 39 | cdef list _all_imports 40 | cdef _StringArrayPtr _last_import_array 41 | cpdef _parse_disk_file(self, displayName, diskPath, imports) 42 | 43 | cdef class _DynamicOrphan: 44 | cdef C_DynamicOrphan thisptr 45 | cdef public object _parent 46 | 47 | cdef _init(self, C_DynamicOrphan other, object parent) 48 | 49 | cdef C_DynamicOrphan move(self) 50 | cpdef get(self) 51 | 52 | 53 | cdef class _DynamicStructReader: 54 | cdef C_DynamicStruct.Reader thisptr 55 | cdef public object _parent 56 | cdef public bint is_root 57 | cdef object _obj_to_pin 58 | cdef object _schema 59 | 60 | cdef _init(self, C_DynamicStruct.Reader other, object parent, bint isRoot=?, bint tryRegistry=?) 61 | 62 | cpdef _get(self, field) 63 | cpdef _has(self, field) 64 | cpdef _DynamicEnumField _which(self) 65 | cpdef _which_str(self) 66 | cpdef _get_by_field(self, _StructSchemaField field) 67 | cpdef _has_by_field(self, _StructSchemaField field) 68 | 69 | cpdef as_builder(self, num_first_segment_words=?) 70 | 71 | 72 | cdef class _DynamicStructBuilder: 73 | cdef DynamicStruct_Builder thisptr 74 | cdef public object _parent 75 | cdef public bint is_root 76 | cdef public bint _is_written 77 | cdef object _schema 78 | 79 | cdef _init(self, DynamicStruct_Builder other, object parent, bint isRoot=?, bint tryRegistry=?) 80 | 81 | cdef _check_write(self) 82 | cpdef to_bytes(_DynamicStructBuilder self) 83 | cpdef to_segments(_DynamicStructBuilder self) 84 | cpdef _to_bytes_packed_helper(_DynamicStructBuilder self, word_count) 85 | cpdef to_bytes_packed(_DynamicStructBuilder self) 86 | 87 | cpdef _get(self, field) 88 | cpdef _set(self, field, value) 89 | cpdef _has(self, field) 90 | cpdef init(self, field, size=?) 91 | cpdef _get_by_field(self, _StructSchemaField field) 92 | cpdef _set_by_field(self, _StructSchemaField field, value) 93 | cpdef _has_by_field(self, _StructSchemaField field) 94 | cpdef _init_by_field(self, _StructSchemaField field, size=?) 95 | cpdef init_resizable_list(self, field) 96 | cpdef _DynamicEnumField _which(self) 97 | cpdef _which_str(self) 98 | cpdef adopt(self, field, _DynamicOrphan orphan) 99 | cpdef disown(self, field) 100 | 101 | cpdef as_reader(self) 102 | cpdef copy(self, num_first_segment_words=?) 103 | 104 | cdef class _DynamicEnumField: 105 | cdef object thisptr 106 | 107 | cdef _init(self, proto) 108 | cpdef _str(self) 109 | 110 | cdef class _Schema: 111 | cdef C_Schema thisptr 112 | 113 | cdef _init(self, C_Schema other) 114 | 115 | cpdef as_const_value(self) 116 | cpdef as_struct(self) 117 | cpdef as_interface(self) 118 | cpdef as_enum(self) 119 | cpdef get_proto(self) 120 | 121 | cdef class _InterfaceSchema: 122 | cdef C_InterfaceSchema thisptr 123 | cdef object __method_names, __method_names_inherited, __methods, __methods_inherited 124 | cdef _init(self, C_InterfaceSchema other) 125 | 126 | cdef class _DynamicEnum: 127 | cdef capnp.DynamicEnum thisptr 128 | cdef public object _parent 129 | 130 | cdef _init(self, capnp.DynamicEnum other, object parent) 131 | cpdef _as_str(self) 132 | 133 | cdef class _DynamicListBuilder: 134 | cdef C_DynamicList.Builder thisptr 135 | cdef public object _parent 136 | cdef _init(self, C_DynamicList.Builder other, object parent) 137 | 138 | cpdef _get(self, int64_t index) 139 | cpdef _set(self, index, value) 140 | 141 | cpdef adopt(self, index, _DynamicOrphan orphan) 142 | cpdef disown(self, index) 143 | 144 | cpdef init(self, index, size) 145 | 146 | cdef class _MessageBuilder: 147 | cdef schema_cpp.MessageBuilder * thisptr 148 | cpdef init_root(self, schema) 149 | cpdef get_root(self, schema) 150 | cpdef get_root_as_any(self) 151 | cpdef set_root(self, value) 152 | cpdef get_segments_for_output(self) 153 | cpdef new_orphan(self, schema) 154 | 155 | cdef to_python_reader(C_DynamicValue.Reader self, object parent) 156 | cdef to_python_builder(C_DynamicValue.Builder self, object parent) 157 | cdef _to_dict(msg, bint verbose, bint ordered) 158 | cdef _from_list(_DynamicListBuilder msg, list d) 159 | cdef _from_tuple(_DynamicListBuilder msg, tuple d) 160 | cdef _setDynamicFieldWithField(DynamicStruct_Builder thisptr, _StructSchemaField field, value, parent) 161 | cdef _setDynamicFieldStatic(DynamicStruct_Builder thisptr, field, value, parent) 162 | 163 | cdef api object wrap_dynamic_struct_reader(Response & r) with gil 164 | cdef api Promise[void] * call_server_method( 165 | object server, char * _method_name, CallContext & _context, object kj_loop) except * with gil 166 | cdef api object wrap_kj_exception(capnp.Exception & exception) with gil 167 | cdef api object wrap_kj_exception_for_reraise(capnp.Exception & exception) with gil 168 | cdef api object get_exception_info(object exc_type, object exc_obj, object exc_tb) with gil 169 | -------------------------------------------------------------------------------- /capnp/lib/pickle_helper.py: -------------------------------------------------------------------------------- 1 | import capnp 2 | 3 | 4 | def _struct_reducer(schema_id, data): 5 | 'Hack to deal with pypy not allowing reduce functions to be "built-in" methods (ie. compiled from a .pyx)' 6 | with capnp._global_schema_parser.modules_by_id[schema_id].from_bytes(data) as msg: 7 | return msg 8 | -------------------------------------------------------------------------------- /capnp/templates/setup.py.tmpl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from distutils.core import setup 3 | from Cython.Build import cythonize 4 | from shutil import copyfile 5 | import os 6 | import re 7 | 8 | 9 | files = [{% for f in code.requestedFiles %}"{{f.filename}}", {% endfor %}] 10 | 11 | for f in files: 12 | cpp_file = f + '.cpp' 13 | cplus_file = f + '.c++' 14 | cpp_mod = 0 15 | try: 16 | cpp_mod = os.path.getmtime(cpp_file) 17 | except: 18 | pass 19 | cplus_mod = 0 20 | try: 21 | cplus_mod = os.path.getmtime(cplus_file) 22 | except: 23 | pass 24 | if not os.path.exists(cpp_file) or cpp_mod < cplus_mod: 25 | if not os.path.exists(cplus_file): 26 | raise RuntimeError("You need to run `capnp compile -oc++` in addition to `-ocython` first.") 27 | copyfile(cplus_file, cpp_file) 28 | 29 | with open(f + '.h', "r") as file: 30 | lines = file.readlines() 31 | with open(f + '.h', "w") as file: 32 | for line in lines: 33 | file.write(re.sub(r'Builder\(\)\s*=\s*delete;', 'Builder() = default;', line)) 34 | 35 | setup( 36 | name="{{code.requestedFiles[0] | replace('.', '_')}}", 37 | ext_modules=cythonize('*_capnp_cython.pyx', language="c++") 38 | ) 39 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/capnp.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/capnp.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/capnp" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/capnp" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/_templates/versioning.html: -------------------------------------------------------------------------------- 1 | {% if versions %} 2 |

{{ _('Versions') }}

3 |
    4 | {%- for item in versions %} 5 |
  • {{ item.name }}
  • 6 | {%- endfor %} 7 |
8 | {% endif %} 9 | -------------------------------------------------------------------------------- /docs/capnp.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | API Reference 4 | ============= 5 | 6 | .. automodule:: capnp 7 | 8 | .. currentmodule:: capnp 9 | 10 | 11 | Classes 12 | ------- 13 | 14 | RPC 15 | ~~~ 16 | 17 | .. autoclass:: capnp.lib.capnp._RemotePromise 18 | :members: 19 | :undoc-members: 20 | :inherited-members: 21 | 22 | Communication 23 | ############# 24 | 25 | .. autoclass:: TwoPartyClient 26 | :members: 27 | :undoc-members: 28 | :inherited-members: 29 | 30 | .. autoclass:: TwoPartyServer 31 | :members: 32 | :undoc-members: 33 | :inherited-members: 34 | 35 | .. autoclass:: AsyncIoStream 36 | :members: 37 | :undoc-members: 38 | :inherited-members: 39 | 40 | .. autoclass:: capnp.lib.capnp._AsyncIoStream 41 | :members: 42 | :undoc-members: 43 | :inherited-members: 44 | 45 | Capability 46 | ########## 47 | 48 | .. autoclass:: capnp.lib.capnp._DynamicCapabilityClient 49 | :members: 50 | :undoc-members: 51 | :inherited-members: 52 | 53 | 54 | Response 55 | ######## 56 | 57 | .. autoclass:: capnp.lib.capnp._Response 58 | :members: 59 | :undoc-members: 60 | :inherited-members: 61 | 62 | 63 | Miscellaneous 64 | ~~~~~~~~~~~~~ 65 | .. autoclass:: KjException 66 | :members: 67 | :undoc-members: 68 | :inherited-members: 69 | 70 | .. autoclass:: SchemaParser 71 | :members: 72 | :undoc-members: 73 | :inherited-members: 74 | 75 | .. autoclass:: SchemaLoader 76 | :members: 77 | :undoc-members: 78 | :inherited-members: 79 | 80 | Functions 81 | --------- 82 | .. autofunction:: add_import_hook 83 | .. autofunction:: remove_import_hook 84 | .. autofunction:: cleanup_global_schema_parser 85 | 86 | .. autofunction:: kj_loop 87 | .. autofunction:: run 88 | 89 | .. autofunction:: load 90 | 91 | 92 | 93 | Internal Classes 94 | ---------------- 95 | These classes are internal to the library. You will never need to allocate 96 | one yourself, but you may end up using some of their member methods. 97 | 98 | Modules 99 | ~~~~~~~ 100 | These are classes that are made for you when you import a Cap'n Proto file:: 101 | 102 | import capnp 103 | import addressbook_capnp 104 | 105 | print type(addressbook_capnp.Person) # capnp.capnp._StructModule 106 | 107 | .. autoclass:: _InterfaceModule 108 | :members: 109 | :undoc-members: 110 | :inherited-members: 111 | 112 | .. autoclass:: _StructModule 113 | :members: 114 | :undoc-members: 115 | :inherited-members: 116 | 117 | Readers 118 | ~~~~~~~ 119 | .. autoclass:: _DynamicListReader 120 | :members: 121 | :undoc-members: 122 | :inherited-members: 123 | 124 | .. autoclass:: _DynamicStructReader 125 | :members: 126 | :undoc-members: 127 | :inherited-members: 128 | 129 | .. autoclass:: _PackedFdMessageReader 130 | :members: 131 | :undoc-members: 132 | :inherited-members: 133 | 134 | .. autoclass:: _StreamFdMessageReader 135 | :members: 136 | :undoc-members: 137 | :inherited-members: 138 | 139 | Builders 140 | ~~~~~~~~ 141 | .. autoclass:: _DynamicResizableListBuilder 142 | :members: 143 | :undoc-members: 144 | :inherited-members: 145 | 146 | .. autoclass:: _DynamicListBuilder 147 | :members: 148 | :undoc-members: 149 | :inherited-members: 150 | 151 | .. autoclass:: _DynamicStructBuilder 152 | :members: 153 | :undoc-members: 154 | :inherited-members: 155 | 156 | .. autoclass:: _MallocMessageBuilder 157 | :members: 158 | :undoc-members: 159 | :inherited-members: 160 | 161 | RPC 162 | ~~~ 163 | .. autoclass:: _CapabilityClient 164 | :members: 165 | :undoc-members: 166 | :inherited-members: 167 | 168 | .. autoclass:: _DynamicCapabilityClient 169 | :members: 170 | :undoc-members: 171 | :inherited-members: 172 | 173 | Miscellaneous 174 | ~~~~~~~~~~~~~ 175 | .. autoclass:: _DynamicOrphan 176 | :members: 177 | :undoc-members: 178 | :inherited-members: 179 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. capnp documentation master file 2 | 3 | pycapnp 4 | ======= 5 | 6 | This is a python wrapping of the C++ implementation of the `Cap'n Proto `_ library. Here is a short description, quoted from its docs: 7 | 8 | Cap’n Proto is an insanely fast data interchange format and capability-based RPC system. Think JSON, except binary. Or think Protocol Buffers, except faster. In fact, in benchmarks, Cap’n Proto is INFINITY TIMES faster than Protocol Buffers. 9 | 10 | Since the python library is just a thin wrapping of the C++ library, we inherit a lot of what makes Cap'n Proto fast. In some simplistic benchmarks (available in the `benchmark directory of the repo `_), pycapnp has proven to be decently faster than Protocol Buffers (both pure python and C++ implementations). Also, the python capnp library can load Cap'n Proto schema files directly, without the need for a seperate compile step like with Protocol Buffers or Thrift. pycapnp is available on `github `_ and `pypi `_. 11 | 12 | Contents: 13 | 14 | .. toctree:: 15 | :maxdepth: 4 16 | 17 | install 18 | quickstart 19 | capnp 20 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Installation 4 | ============ 5 | 6 | Pip 7 | --- 8 | The pip installation will using the binary versions of the package (if possible). These contain a bundled version of capnproto (on Linux compiled with `manylinux `_). Starting from v1.0.0b1 binary releases are available for Windows, macOS and Linux from `pypi `_:: 9 | 10 | [sudo] pip install pycapnp 11 | 12 | To force rebuilding the pip package from source (you'll need requirments.txt or pipenv):: 13 | 14 | pip install --no-binary :all: pycapnp 15 | 16 | To force bundling libcapnp (or force system libcapnp), just in case pip isn't doing the right thing:: 17 | 18 | pip install --no-binary :all: -C force-bundled-libcapnp=True 19 | pip install --no-binary :all: -C force-system-libcapnp=True 20 | 21 | If you're using an older Linux distro (e.g. CentOS 6) you many need to set `LDFLAGS="-Wl,--no-as-needed -lrt"`:: 22 | 23 | LDFLAGS="-Wl,--no-as-needed -lrt" pip install --no-binary :all: pycapnp 24 | 25 | It's also possible to specify the libcapnp url when bundling (this may not work, there be dragons):: 26 | 27 | pip install --no-binary :all: -C force-bundled-libcapnp=True -C libcapnp-url="https://github.com/capnproto/capnproto/archive/master.tar.gz" 28 | 29 | From Source 30 | ----------- 31 | Source installation is generally not needed unless you're looking into an issue with capnproto or pycapnp itself. 32 | 33 | C++ Cap'n Proto Library 34 | ~~~~~~~~~~~~~~~~~~~~~~~ 35 | You need to install the C++ Cap'n Proto library first. It requires a C++ compiler with C++14 support, such as GCC 5+ or Clang 5+. Follow installation docs at `https://capnproto.org/install.html `_. 36 | 37 | pycapnp from git 38 | ~~~~~~~~~~~~~~~~ 39 | If you want the latest development version, you can clone the github repo:: 40 | 41 | git clone https://github.com/capnproto/pycapnp.git 42 | 43 | For development packages use one of the following to install the python dependencies:: 44 | 45 | pipenv install 46 | pip install -r requirements.txt 47 | 48 | And install pycapnp with:: 49 | 50 | cd pycapnp 51 | pip install . 52 | 53 | 54 | Development 55 | ----------- 56 | Clone the repo from https://github.com/capnproto/pycapnp.git:: 57 | 58 | git clone https://github.com/capnproto/pycapnp.git 59 | 60 | For development packages use one of the following to install the python dependencies:: 61 | 62 | pipenv install 63 | pip install -r requirements.txt 64 | 65 | Building:: 66 | 67 | cd pycapnp 68 | pip install . 69 | 70 | Useful targets for setup.py:: 71 | 72 | python setup.py clean 73 | 74 | Useful command-line arguments are available for pip install:: 75 | 76 | -C force-bundled-libcapnp=True 77 | -C force-system-libcapnp=True 78 | -C libcapnp-url="https://github.com/capnproto/capnproto/archive/master.tar.gz" 79 | 80 | Testing is done through pytest:: 81 | 82 | cd pycapnp 83 | pytest 84 | pytest test/test_rpc_calculator.py 85 | 86 | Once you're done installing, take a look at the :ref:`quickstart` 87 | -------------------------------------------------------------------------------- /examples/addressbook.capnp: -------------------------------------------------------------------------------- 1 | @0x934efea7f017fff0; 2 | 3 | const qux :UInt32 = 123; 4 | 5 | struct Person { 6 | id @0 :UInt32; 7 | name @1 :Text; 8 | email @2 :Text; 9 | phones @3 :List(PhoneNumber); 10 | 11 | struct PhoneNumber { 12 | number @0 :Text; 13 | type @1 :Type; 14 | 15 | enum Type { 16 | mobile @0; 17 | home @1; 18 | work @2; 19 | } 20 | } 21 | employment :union { 22 | unemployed @4 :Void; 23 | employer @5 :Text; 24 | school @6 :Text; 25 | selfEmployed @7 :Void; 26 | # We assume that a person is only one of these. 27 | } 28 | 29 | testGroup :group { 30 | field1 @8 :UInt32; 31 | field2 @9 :UInt32; 32 | field3 @10 :UInt32; 33 | } 34 | extraData @11 :Data; 35 | } 36 | 37 | struct AddressBook { 38 | people @0 :List(Person); 39 | } 40 | 41 | -------------------------------------------------------------------------------- /examples/addressbook.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import capnp # noqa: F401 4 | 5 | import addressbook_capnp 6 | 7 | 8 | def writeAddressBook(file): 9 | addresses = addressbook_capnp.AddressBook.new_message() 10 | people = addresses.init("people", 2) 11 | 12 | alice = people[0] 13 | alice.id = 123 14 | alice.name = "Alice" 15 | alice.email = "alice@example.com" 16 | alicePhones = alice.init("phones", 1) 17 | alicePhones[0].number = "555-1212" 18 | alicePhones[0].type = "mobile" 19 | alice.employment.school = "MIT" 20 | 21 | bob = people[1] 22 | bob.id = 456 23 | bob.name = "Bob" 24 | bob.email = "bob@example.com" 25 | bobPhones = bob.init("phones", 2) 26 | bobPhones[0].number = "555-4567" 27 | bobPhones[0].type = "home" 28 | bobPhones[1].number = "555-7654" 29 | bobPhones[1].type = "work" 30 | bob.employment.unemployed = None 31 | 32 | addresses.write(file) 33 | 34 | 35 | def printAddressBook(file): 36 | addresses = addressbook_capnp.AddressBook.read(file) 37 | 38 | for person in addresses.people: 39 | print(person.name, ":", person.email) 40 | for phone in person.phones: 41 | print(phone.type, ":", phone.number) 42 | 43 | which = person.employment.which() 44 | print(which) 45 | 46 | if which == "unemployed": 47 | print("unemployed") 48 | elif which == "employer": 49 | print("employer:", person.employment.employer) 50 | elif which == "school": 51 | print("student at:", person.employment.school) 52 | elif which == "selfEmployed": 53 | print("self employed") 54 | print() 55 | 56 | 57 | if __name__ == "__main__": 58 | f = open("example", "w") 59 | writeAddressBook(f) 60 | 61 | f = open("example", "r") 62 | printAddressBook(f) 63 | -------------------------------------------------------------------------------- /examples/async_calculator_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import asyncio 5 | import logging 6 | 7 | import capnp 8 | import calculator_capnp 9 | 10 | 11 | logger = logging.getLogger(__name__) 12 | logger.setLevel(logging.DEBUG) 13 | 14 | 15 | async def evaluate_impl(expression, params=None): 16 | """Implementation of CalculatorImpl::evaluate(), also shared by 17 | FunctionImpl::call(). In the latter case, `params` are the parameter 18 | values passed to the function; in the former case, `params` is just an 19 | empty list.""" 20 | 21 | which = expression.which() 22 | 23 | if which == "literal": 24 | return expression.literal 25 | elif which == "previousResult": 26 | return (await expression.previousResult.read()).value 27 | elif which == "parameter": 28 | assert expression.parameter < len(params) 29 | return params[expression.parameter] 30 | elif which == "call": 31 | call = expression.call 32 | func = call.function 33 | 34 | # Evaluate each parameter. 35 | paramPromises = [evaluate_impl(param, params) for param in call.params] 36 | vals = await asyncio.gather(*paramPromises) 37 | 38 | # When the parameters are complete, call the function. 39 | result = await func.call(vals) 40 | return result.value 41 | else: 42 | raise ValueError("Unknown expression type: " + which) 43 | 44 | 45 | class ValueImpl(calculator_capnp.Calculator.Value.Server): 46 | "Simple implementation of the Calculator.Value Cap'n Proto interface." 47 | 48 | def __init__(self, value): 49 | self.value = value 50 | 51 | async def read(self, **kwargs): 52 | return self.value 53 | 54 | 55 | class FunctionImpl(calculator_capnp.Calculator.Function.Server): 56 | """Implementation of the Calculator.Function Cap'n Proto interface, where the 57 | function is defined by a Calculator.Expression.""" 58 | 59 | def __init__(self, paramCount, body): 60 | self.paramCount = paramCount 61 | self.body = body.as_builder() 62 | 63 | async def call(self, params, _context, **kwargs): 64 | """Note that we're returning a Promise object here, and bypassing the 65 | helper functionality that normally sets the results struct from the 66 | returned object. Instead, we set _context.results directly inside of 67 | another promise""" 68 | 69 | assert len(params) == self.paramCount 70 | return await evaluate_impl(self.body, params) 71 | 72 | 73 | class OperatorImpl(calculator_capnp.Calculator.Function.Server): 74 | """Implementation of the Calculator.Function Cap'n Proto interface, wrapping 75 | basic binary arithmetic operators.""" 76 | 77 | def __init__(self, op): 78 | self.op = op 79 | 80 | async def call(self, params, **kwargs): 81 | assert len(params) == 2 82 | 83 | op = self.op 84 | 85 | if op == "add": 86 | return params[0] + params[1] 87 | elif op == "subtract": 88 | return params[0] - params[1] 89 | elif op == "multiply": 90 | return params[0] * params[1] 91 | elif op == "divide": 92 | return params[0] / params[1] 93 | else: 94 | raise ValueError("Unknown operator") 95 | 96 | 97 | class CalculatorImpl(calculator_capnp.Calculator.Server): 98 | "Implementation of the Calculator Cap'n Proto interface." 99 | 100 | async def evaluate(self, expression, _context, **kwargs): 101 | return ValueImpl(await evaluate_impl(expression)) 102 | 103 | async def defFunction(self, paramCount, body, _context, **kwargs): 104 | return FunctionImpl(paramCount, body) 105 | 106 | async def getOperator(self, op, **kwargs): 107 | return OperatorImpl(op) 108 | 109 | 110 | async def new_connection(stream): 111 | await capnp.TwoPartyServer(stream, bootstrap=CalculatorImpl()).on_disconnect() 112 | 113 | 114 | def parse_args(): 115 | parser = argparse.ArgumentParser( 116 | usage="""Runs the server bound to the given address/port ADDRESS. """ 117 | ) 118 | 119 | parser.add_argument("address", help="ADDRESS:PORT") 120 | 121 | return parser.parse_args() 122 | 123 | 124 | async def main(): 125 | host, port = parse_args().address.split(":") 126 | server = await capnp.AsyncIoStream.create_server(new_connection, host, port) 127 | async with server: 128 | await server.serve_forever() 129 | 130 | 131 | if __name__ == "__main__": 132 | asyncio.run(capnp.run(main())) 133 | -------------------------------------------------------------------------------- /examples/async_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import asyncio 4 | import argparse 5 | import time 6 | import capnp 7 | 8 | import thread_capnp 9 | 10 | 11 | def parse_args(): 12 | parser = argparse.ArgumentParser( 13 | usage="Connects to the Example thread server at the given address and does some RPCs" 14 | ) 15 | parser.add_argument("host", help="HOST:PORT") 16 | 17 | return parser.parse_args() 18 | 19 | 20 | class StatusSubscriber(thread_capnp.Example.StatusSubscriber.Server): 21 | """An implementation of the StatusSubscriber interface""" 22 | 23 | async def status(self, value, **kwargs): 24 | print("status: {}".format(time.time())) 25 | 26 | 27 | async def main(host): 28 | host, port = host.split(":") 29 | connection = await capnp.AsyncIoStream.create_connection(host=host, port=port) 30 | client = capnp.TwoPartyClient(connection) 31 | cap = client.bootstrap().cast_as(thread_capnp.Example) 32 | 33 | # Start background task for subscriber 34 | task = asyncio.ensure_future(cap.subscribeStatus(StatusSubscriber())) 35 | 36 | # Run blocking tasks 37 | print("main: {}".format(time.time())) 38 | await cap.longRunning() 39 | print("main: {}".format(time.time())) 40 | await cap.longRunning() 41 | print("main: {}".format(time.time())) 42 | await cap.longRunning() 43 | print("main: {}".format(time.time())) 44 | 45 | task.cancel() 46 | 47 | 48 | if __name__ == "__main__": 49 | args = parse_args() 50 | asyncio.run(capnp.run(main(args.host))) 51 | 52 | # Test that we can run multiple asyncio loops in sequence. This is particularly tricky, because 53 | # main contains a background task that we never cancel. The entire loop gets cleaned up anyways, 54 | # and we can start a new loop. 55 | asyncio.run(capnp.run(main(args.host))) 56 | -------------------------------------------------------------------------------- /examples/async_reconnecting_ssl_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import asyncio 4 | import argparse 5 | import os 6 | import time 7 | import ssl 8 | import socket 9 | 10 | import capnp 11 | 12 | import thread_capnp 13 | 14 | this_dir = os.path.dirname(os.path.abspath(__file__)) 15 | 16 | 17 | def parse_args(): 18 | parser = argparse.ArgumentParser( 19 | usage="Connects to the Example thread server at the given address and does some RPCs" 20 | ) 21 | parser.add_argument("host", help="HOST:PORT") 22 | 23 | return parser.parse_args() 24 | 25 | 26 | class StatusSubscriber(thread_capnp.Example.StatusSubscriber.Server): 27 | """An implementation of the StatusSubscriber interface""" 28 | 29 | def status(self, value, **kwargs): 30 | print("status: {}".format(time.time())) 31 | 32 | 33 | async def watch_connection(cap): 34 | while True: 35 | try: 36 | await asyncio.wait_for(cap.alive(), timeout=5) 37 | await asyncio.sleep(1) 38 | except asyncio.TimeoutError: 39 | print("Watch timeout!") 40 | asyncio.get_running_loop().stop() 41 | return False 42 | 43 | 44 | async def main(host): 45 | addr, port = host.split(":") 46 | 47 | # Setup SSL context 48 | ctx = ssl.create_default_context( 49 | ssl.Purpose.SERVER_AUTH, cafile=os.path.join(this_dir, "selfsigned.cert") 50 | ) 51 | 52 | # Handle both IPv4 and IPv6 cases 53 | try: 54 | print("Try IPv4") 55 | stream = await capnp.AsyncIoStream.create_connection( 56 | addr, port, ssl=ctx, family=socket.AF_INET 57 | ) 58 | except Exception: 59 | print("Try IPv6") 60 | stream = await capnp.AsyncIoStream.create_connection( 61 | addr, port, ssl=ctx, family=socket.AF_INET6 62 | ) 63 | 64 | client = capnp.TwoPartyClient(stream) 65 | cap = client.bootstrap().cast_as(thread_capnp.Example) 66 | 67 | # Start watcher to restart socket connection if it is lost and subscriber background task 68 | background_tasks = asyncio.gather( 69 | cap.subscribeStatus(StatusSubscriber()), 70 | watch_connection(cap), 71 | return_exceptions=True, 72 | ) 73 | 74 | # Run blocking tasks 75 | print("main: {}".format(time.time())) 76 | await cap.longRunning() 77 | print("main: {}".format(time.time())) 78 | await cap.longRunning() 79 | print("main: {}".format(time.time())) 80 | await cap.longRunning() 81 | print("main: {}".format(time.time())) 82 | 83 | background_tasks.cancel() 84 | 85 | return True 86 | 87 | 88 | if __name__ == "__main__": 89 | # Using asyncio.run hits an asyncio ssl bug 90 | # https://bugs.python.org/issue36709 91 | # asyncio.run(main(parse_args().host), loop=loop, debug=True) 92 | retry = True 93 | while retry: 94 | loop = asyncio.new_event_loop() 95 | try: 96 | retry = not loop.run_until_complete(capnp.run(main(parse_args().host))) 97 | except RuntimeError: 98 | # If an IO is hung, the event loop will be stopped 99 | # and will throw RuntimeError exception 100 | continue 101 | if retry: 102 | time.sleep(1) 103 | print("Retrying...") 104 | 105 | # How this works 106 | # - There are two retry mechanisms 107 | # 1. Connection retry 108 | # 2. alive RPC verification 109 | # - The connection retry just loops the connection (IPv4+IPv6 until there is a connection or Ctrl+C) 110 | # - The alive RPC verification attempts a very basic rpc call with a timeout 111 | # * If there is a timeout, stop the current event loop 112 | # * Use the RuntimeError exception to force a reconnect 113 | # * myreader and mywriter must also be wrapped in wait_for in order for the events to get triggered correctly 114 | -------------------------------------------------------------------------------- /examples/async_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import asyncio 5 | import logging 6 | 7 | import capnp 8 | import thread_capnp 9 | 10 | 11 | logger = logging.getLogger(__name__) 12 | logger.setLevel(logging.DEBUG) 13 | 14 | 15 | class ExampleImpl(thread_capnp.Example.Server): 16 | "Implementation of the Example threading Cap'n Proto interface." 17 | 18 | async def subscribeStatus(self, subscriber, **kwargs): 19 | await asyncio.sleep(0.1) 20 | await subscriber.status(True) 21 | await self.subscribeStatus(subscriber) 22 | 23 | async def longRunning(self, **kwargs): 24 | await asyncio.sleep(0.1) 25 | 26 | 27 | async def new_connection(stream): 28 | await capnp.TwoPartyServer(stream, bootstrap=ExampleImpl()).on_disconnect() 29 | 30 | 31 | def parse_args(): 32 | parser = argparse.ArgumentParser( 33 | usage="""Runs the server bound to the given address/port ADDRESS. """ 34 | ) 35 | 36 | parser.add_argument("address", help="ADDRESS:PORT") 37 | 38 | return parser.parse_args() 39 | 40 | 41 | async def main(): 42 | host, port = parse_args().address.split(":") 43 | server = await capnp.AsyncIoStream.create_server(new_connection, host, port) 44 | async with server: 45 | await server.serve_forever() 46 | 47 | 48 | if __name__ == "__main__": 49 | asyncio.run(capnp.run(main())) 50 | -------------------------------------------------------------------------------- /examples/async_socket_message_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import asyncio 4 | import argparse 5 | import capnp 6 | 7 | import addressbook_capnp 8 | 9 | 10 | def parse_args(): 11 | parser = argparse.ArgumentParser( 12 | usage="Connects to the Example thread server at the given address and does some RPCs" 13 | ) 14 | parser.add_argument("host", help="HOST:PORT") 15 | 16 | return parser.parse_args() 17 | 18 | 19 | async def writeAddressBook(stream, bob_id): 20 | addresses = addressbook_capnp.AddressBook.new_message() 21 | people = addresses.init("people", 1) 22 | 23 | bob = people[0] 24 | bob.id = bob_id 25 | bob.name = "Bob" 26 | bob.email = "bob@example.com" 27 | bobPhones = bob.init("phones", 2) 28 | bobPhones[0].number = "555-4567" 29 | bobPhones[0].type = "home" 30 | bobPhones[1].number = "555-7654" 31 | bobPhones[1].type = "work" 32 | bob.employment.unemployed = None 33 | 34 | await addresses.write_async(stream) 35 | 36 | 37 | async def main(host): 38 | host, port = host.split(":") 39 | stream = await capnp.AsyncIoStream.create_connection(host=host, port=port) 40 | 41 | await writeAddressBook(stream, 0) 42 | 43 | message = await addressbook_capnp.AddressBook.read_async(stream) 44 | print(message) 45 | assert message.people[0].name == "Alice" 46 | assert message.people[0].id == 0 47 | 48 | await writeAddressBook(stream, 1) 49 | 50 | 51 | if __name__ == "__main__": 52 | args = parse_args() 53 | asyncio.run(capnp.run(main(args.host))) 54 | -------------------------------------------------------------------------------- /examples/async_socket_message_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import asyncio 5 | 6 | import capnp 7 | import addressbook_capnp 8 | 9 | 10 | async def writeAddressBook(stream, alice_id): 11 | addresses = addressbook_capnp.AddressBook.new_message() 12 | people = addresses.init("people", 1) 13 | 14 | alice = people[0] 15 | alice.id = alice_id 16 | alice.name = "Alice" 17 | alice.email = "alice@example.com" 18 | alicePhones = alice.init("phones", 1) 19 | alicePhones[0].number = "555-1212" 20 | alicePhones[0].type = "mobile" 21 | alice.employment.school = "MIT" 22 | 23 | await addresses.write_async(stream) 24 | 25 | 26 | async def new_connection(stream): 27 | message = await addressbook_capnp.AddressBook.read_async(stream) 28 | print(message) 29 | assert message.people[0].name == "Bob" 30 | assert message.people[0].id == 0 31 | 32 | await writeAddressBook(stream, 0) 33 | 34 | message = await addressbook_capnp.AddressBook.read_async(stream) 35 | print(message) 36 | assert message.people[0].name == "Bob" 37 | assert message.people[0].id == 1 38 | 39 | message = await addressbook_capnp.AddressBook.read_async(stream) 40 | print(message) 41 | assert message is None 42 | 43 | 44 | def parse_args(): 45 | parser = argparse.ArgumentParser( 46 | usage="""Runs the server bound to the given address/port ADDRESS. """ 47 | ) 48 | 49 | parser.add_argument("address", help="ADDRESS:PORT") 50 | 51 | return parser.parse_args() 52 | 53 | 54 | async def main(): 55 | host, port = parse_args().address.split(":") 56 | server = await capnp.AsyncIoStream.create_server(new_connection, host, port) 57 | async with server: 58 | await server.serve_forever() 59 | 60 | 61 | if __name__ == "__main__": 62 | asyncio.run(capnp.run(main())) 63 | -------------------------------------------------------------------------------- /examples/async_ssl_calculator_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import asyncio 5 | import logging 6 | import os 7 | import ssl 8 | import socket 9 | 10 | import capnp 11 | import calculator_capnp 12 | 13 | 14 | logger = logging.getLogger(__name__) 15 | logger.setLevel(logging.DEBUG) 16 | 17 | this_dir = os.path.dirname(os.path.abspath(__file__)) 18 | 19 | 20 | async def evaluate_impl(expression, params=None): 21 | """Implementation of CalculatorImpl::evaluate(), also shared by 22 | FunctionImpl::call(). In the latter case, `params` are the parameter 23 | values passed to the function; in the former case, `params` is just an 24 | empty list.""" 25 | 26 | which = expression.which() 27 | 28 | if which == "literal": 29 | return expression.literal 30 | elif which == "previousResult": 31 | return (await expression.previousResult.read()).value 32 | elif which == "parameter": 33 | assert expression.parameter < len(params) 34 | return params[expression.parameter] 35 | elif which == "call": 36 | call = expression.call 37 | func = call.function 38 | 39 | # Evaluate each parameter. 40 | paramPromises = [evaluate_impl(param, params) for param in call.params] 41 | vals = await asyncio.gather(*paramPromises) 42 | 43 | # When the parameters are complete, call the function. 44 | result = await func.call(vals) 45 | return result.value 46 | else: 47 | raise ValueError("Unknown expression type: " + which) 48 | 49 | 50 | class ValueImpl(calculator_capnp.Calculator.Value.Server): 51 | "Simple implementation of the Calculator.Value Cap'n Proto interface." 52 | 53 | def __init__(self, value): 54 | self.value = value 55 | 56 | async def read(self, **kwargs): 57 | return self.value 58 | 59 | 60 | class FunctionImpl(calculator_capnp.Calculator.Function.Server): 61 | """Implementation of the Calculator.Function Cap'n Proto interface, where the 62 | function is defined by a Calculator.Expression.""" 63 | 64 | def __init__(self, paramCount, body): 65 | self.paramCount = paramCount 66 | self.body = body.as_builder() 67 | 68 | async def call(self, params, _context, **kwargs): 69 | """Note that we're returning a Promise object here, and bypassing the 70 | helper functionality that normally sets the results struct from the 71 | returned object. Instead, we set _context.results directly inside of 72 | another promise""" 73 | 74 | assert len(params) == self.paramCount 75 | return await evaluate_impl(self.body, params) 76 | 77 | 78 | class OperatorImpl(calculator_capnp.Calculator.Function.Server): 79 | """Implementation of the Calculator.Function Cap'n Proto interface, wrapping 80 | basic binary arithmetic operators.""" 81 | 82 | def __init__(self, op): 83 | self.op = op 84 | 85 | async def call(self, params, **kwargs): 86 | assert len(params) == 2 87 | 88 | op = self.op 89 | 90 | if op == "add": 91 | return params[0] + params[1] 92 | elif op == "subtract": 93 | return params[0] - params[1] 94 | elif op == "multiply": 95 | return params[0] * params[1] 96 | elif op == "divide": 97 | return params[0] / params[1] 98 | else: 99 | raise ValueError("Unknown operator") 100 | 101 | 102 | class CalculatorImpl(calculator_capnp.Calculator.Server): 103 | "Implementation of the Calculator Cap'n Proto interface." 104 | 105 | async def evaluate(self, expression, _context, **kwargs): 106 | return ValueImpl(await evaluate_impl(expression)) 107 | 108 | async def defFunction(self, paramCount, body, _context, **kwargs): 109 | return FunctionImpl(paramCount, body) 110 | 111 | async def getOperator(self, op, **kwargs): 112 | return OperatorImpl(op) 113 | 114 | 115 | def parse_args(): 116 | parser = argparse.ArgumentParser( 117 | usage="""Runs the server bound to the given address/port ADDRESS. """ 118 | ) 119 | 120 | parser.add_argument("address", help="ADDRESS:PORT") 121 | 122 | return parser.parse_args() 123 | 124 | 125 | async def new_connection(stream): 126 | await capnp.TwoPartyServer(stream, bootstrap=CalculatorImpl()).on_disconnect() 127 | 128 | 129 | async def main(): 130 | host, port = parse_args().address.split(":") 131 | 132 | # Setup SSL context 133 | ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) 134 | ctx.load_cert_chain( 135 | os.path.join(this_dir, "selfsigned.cert"), 136 | os.path.join(this_dir, "selfsigned.key"), 137 | ) 138 | 139 | # Handle both IPv4 and IPv6 cases 140 | try: 141 | print("Try IPv4") 142 | server = await capnp.AsyncIoStream.create_server( 143 | new_connection, host, port, ssl=ctx, family=socket.AF_INET 144 | ) 145 | except Exception: 146 | print("Try IPv6") 147 | server = await capnp.AsyncIoStream.create_server( 148 | new_connection, host, port, ssl=ctx, family=socket.AF_INET6 149 | ) 150 | 151 | async with server: 152 | await server.serve_forever() 153 | 154 | 155 | if __name__ == "__main__": 156 | asyncio.run(capnp.run(main())) 157 | -------------------------------------------------------------------------------- /examples/async_ssl_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import asyncio 5 | import os 6 | import ssl 7 | import time 8 | import socket 9 | 10 | import capnp 11 | import thread_capnp 12 | 13 | this_dir = os.path.dirname(os.path.abspath(__file__)) 14 | 15 | 16 | def parse_args(): 17 | parser = argparse.ArgumentParser( 18 | usage="Connects to the Example thread server at the given address and does some RPCs" 19 | ) 20 | parser.add_argument("host", help="HOST:PORT") 21 | 22 | return parser.parse_args() 23 | 24 | 25 | class StatusSubscriber(thread_capnp.Example.StatusSubscriber.Server): 26 | """An implementation of the StatusSubscriber interface""" 27 | 28 | async def status(self, value, **kwargs): 29 | print("status: {}".format(time.time())) 30 | 31 | 32 | async def main(host): 33 | addr, port = host.split(":") 34 | 35 | # Setup SSL context 36 | ctx = ssl.create_default_context( 37 | ssl.Purpose.SERVER_AUTH, cafile=os.path.join(this_dir, "selfsigned.cert") 38 | ) 39 | 40 | # Handle both IPv4 and IPv6 cases 41 | try: 42 | print("Try IPv4") 43 | stream = await capnp.AsyncIoStream.create_connection( 44 | addr, port, ssl=ctx, family=socket.AF_INET 45 | ) 46 | except Exception: 47 | print("Try IPv6") 48 | stream = await capnp.AsyncIoStream.create_connection( 49 | addr, port, ssl=ctx, family=socket.AF_INET6 50 | ) 51 | 52 | client = capnp.TwoPartyClient(stream) 53 | cap = client.bootstrap().cast_as(thread_capnp.Example) 54 | 55 | # Start background task for subscriber 56 | task = asyncio.ensure_future(cap.subscribeStatus(StatusSubscriber())) 57 | 58 | # Run blocking tasks 59 | print("main: {}".format(time.time())) 60 | await cap.longRunning() 61 | print("main: {}".format(time.time())) 62 | await cap.longRunning() 63 | print("main: {}".format(time.time())) 64 | await cap.longRunning() 65 | print("main: {}".format(time.time())) 66 | 67 | task.cancel() 68 | 69 | 70 | if __name__ == "__main__": 71 | # Using asyncio.run hits an asyncio ssl bug 72 | # https://bugs.python.org/issue36709 73 | # asyncio.run(main(parse_args().host), loop=loop, debug=True) 74 | loop = asyncio.get_event_loop() 75 | loop.run_until_complete(capnp.run(main(parse_args().host))) 76 | -------------------------------------------------------------------------------- /examples/async_ssl_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import asyncio 5 | import logging 6 | import os 7 | import ssl 8 | import socket 9 | 10 | import capnp 11 | import thread_capnp 12 | 13 | 14 | logger = logging.getLogger(__name__) 15 | logger.setLevel(logging.DEBUG) 16 | 17 | this_dir = os.path.dirname(os.path.abspath(__file__)) 18 | 19 | 20 | class ExampleImpl(thread_capnp.Example.Server): 21 | "Implementation of the Example threading Cap'n Proto interface." 22 | 23 | async def subscribeStatus(self, subscriber, **kwargs): 24 | await asyncio.sleep(0.1) 25 | await subscriber.status(True) 26 | await self.subscribeStatus(subscriber) 27 | 28 | async def longRunning(self, **kwargs): 29 | await asyncio.sleep(0.1) 30 | 31 | async def alive(self, **kwargs): 32 | return True 33 | 34 | 35 | async def new_connection(stream): 36 | await capnp.TwoPartyServer(stream, bootstrap=ExampleImpl()).on_disconnect() 37 | 38 | 39 | def parse_args(): 40 | parser = argparse.ArgumentParser( 41 | usage="""Runs the server bound to the given address/port ADDRESS. """ 42 | ) 43 | parser.add_argument("address", help="ADDRESS:PORT") 44 | return parser.parse_args() 45 | 46 | 47 | async def main(): 48 | host, port = parse_args().address.split(":") 49 | 50 | # Setup SSL context 51 | ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) 52 | ctx.load_cert_chain( 53 | os.path.join(this_dir, "selfsigned.cert"), 54 | os.path.join(this_dir, "selfsigned.key"), 55 | ) 56 | 57 | # Handle both IPv4 and IPv6 cases 58 | try: 59 | print("Try IPv4") 60 | server = await capnp.AsyncIoStream.create_server( 61 | new_connection, host, port, ssl=ctx, family=socket.AF_INET 62 | ) 63 | except Exception: 64 | print("Try IPv6") 65 | server = await capnp.AsyncIoStream.create_server( 66 | new_connection, host, port, ssl=ctx, family=socket.AF_INET6 67 | ) 68 | 69 | async with server: 70 | await server.serve_forever() 71 | 72 | 73 | if __name__ == "__main__": 74 | asyncio.run(capnp.run(main())) 75 | -------------------------------------------------------------------------------- /examples/calculator.capnp: -------------------------------------------------------------------------------- 1 | @0x85150b117366d14b; 2 | 3 | interface Calculator { 4 | # A "simple" mathematical calculator, callable via RPC. 5 | # 6 | # But, to show off Cap'n Proto, we add some twists: 7 | # 8 | # - You can use the result from one call as the input to the next 9 | # without a network round trip. To accomplish this, evaluate() 10 | # returns a `Value` object wrapping the actual numeric value. 11 | # This object may be used in a subsequent expression. With 12 | # promise pipelining, the Value can actually be used before 13 | # the evaluate() call that creates it returns! 14 | # 15 | # - You can define new functions, and then call them. This again 16 | # shows off pipelining, but it also gives the client the 17 | # opportunity to define a function on the client side and have 18 | # the server call back to it. 19 | # 20 | # - The basic arithmetic operators are exposed as Functions, and 21 | # you have to call getOperator() to obtain them from the server. 22 | # This again demonstrates pipelining -- using getOperator() to 23 | # get each operator and then using them in evaluate() still 24 | # only takes one network round trip. 25 | 26 | evaluate @0 (expression: Expression) -> (value: Value); 27 | # Evaluate the given expression and return the result. The 28 | # result is returned wrapped in a Value interface so that you 29 | # may pass it back to the server in a pipelined request. To 30 | # actually get the numeric value, you must call read() on the 31 | # Value -- but again, this can be pipelined so that it incurs 32 | # no additional latency. 33 | 34 | struct Expression { 35 | # A numeric expression. 36 | 37 | union { 38 | literal @0 :Float64; 39 | # A literal numeric value. 40 | 41 | previousResult @1 :Value; 42 | # A value that was (or, will be) returned by a previous 43 | # evaluate(). 44 | 45 | parameter @2 :UInt32; 46 | # A parameter to the function (only valid in function bodies; 47 | # see defFunction). 48 | 49 | call :group { 50 | # Call a function on a list of parameters. 51 | function @3 :Function; 52 | params @4 :List(Expression); 53 | } 54 | } 55 | } 56 | 57 | interface Value { 58 | # Wraps a numeric value in an RPC object. This allows the value 59 | # to be used in subsequent evaluate() requests without the client 60 | # waiting for the evaluate() that returns the Value to finish. 61 | 62 | read @0 () -> (value :Float64); 63 | # Read back the raw numeric value. 64 | } 65 | 66 | defFunction @1 (paramCount :Int32, body :Expression) 67 | -> (func :Function); 68 | # Define a function that takes `paramCount` parameters and returns the 69 | # evaluation of `body` after substituting these parameters. 70 | 71 | interface Function { 72 | # An algebraic function. Can be called directly, or can be used inside 73 | # an Expression. 74 | # 75 | # A client can create a Function that runs on the server side using 76 | # `defFunction()` or `getOperator()`. Alternatively, a client can 77 | # implement a Function on the client side and the server will call back 78 | # to it. However, a function defined on the client side will require a 79 | # network round trip whenever the server needs to call it, whereas 80 | # functions defined on the server and then passed back to it are called 81 | # locally. 82 | 83 | call @0 (params :List(Float64)) -> (value: Float64); 84 | # Call the function on the given parameters. 85 | } 86 | 87 | getOperator @2 (op: Operator) -> (func: Function); 88 | # Get a Function representing an arithmetic operator, which can then be 89 | # used in Expressions. 90 | 91 | enum Operator { 92 | add @0; 93 | subtract @1; 94 | multiply @2; 95 | divide @3; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /examples/selfsigned.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFCTCCAvGgAwIBAgIUAd1KPs1sZ9NMbVRLXRM+XKSQ/gUwDQYJKoZIhvcNAQEL 3 | BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MDUxMjE4MjEwOFoXDTM1MDUx 4 | MDE4MjEwOFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF 5 | AAOCAg8AMIICCgKCAgEAz52p5xMMrjqRtwp9oH6m7TOVNkIU2WohUQfhbPgBdpkj 6 | ZJnv6BBJyeUnDfJHayYGA7VPNh3MEWCIzFUok98fSZzIh4qqfSTg+2YdNIfM/WP8 7 | 6jA6+YXutLl+dBZfTLW0DNUsCElgTWFpmi5AD2oqlMqdJKqOh5e8e6LBlpukXcc5 8 | ykNzFbTpkpc99f7k/2zMBe1208EjHXMscXDxuETdlTySo9FtetUxLuc7p6a6g472 9 | 296/HYDaqjYZextKCTpZb4YXAG/+IcRqPi0qfSy4BKsYImo0G6lTIm28zvQBOkU9 10 | hpWw5GtKeWjdsF/Gr780QBY3AeUMX0cC4iU+goq4NLChRC9dde+yHZOUL7UgPAPD 11 | tQYjLZ9pbfi3pkJZ4879YIkDD/M0Zh6LPgstfG9PRNBFEXdb9Ima8ZsxN8YeelsM 12 | ZfmHKcWN8PAnH2ETGxVsXPXUuz1PoIxEh/MWWx1lASqF0bQtLsJP39XGWiRkhg4v 13 | djMv9lk4+uZwZ7oDiXqGEBTzdxGtvFYNjAOWtspumdtP9MwC+oI1IR6S/bG45jtz 14 | kkeiWz/sW1nktjFkSgdvECOBJAQM+RsNEs/9bpQV+Y8di12a/7imsSQacsvkf3Mw 15 | m127zYcdMJVfg84AZCz+EmDaFqfO9OQdsLPtQhL6Qpixd3Mtn1WoxIhhtb+blTUC 16 | AwEAAaNTMFEwHQYDVR0OBBYEFGBQtFxHoLu3xrGrXiulqFXhPHQTMB8GA1UdIwQY 17 | MBaAFGBQtFxHoLu3xrGrXiulqFXhPHQTMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI 18 | hvcNAQELBQADggIBAI84QsV4nUP6nHhHnmrUBDRpUy7VcJVD5rfq3LBSd4j2sKtd 19 | Kv0REUDjYQVAjP6XvrX8Q+pHBrQmtoNoUWCPkp8dl8CpnjB5Jl2QPy2HOyZMmL/7 20 | LJN2KziZ/JprGxX1sYKKu7OQQ45ZwVByTwJ1JQRtQNhCYSV7IdA6nKbaWpnU98Xs 21 | usq0JZQ97HNCYqOHicFl0zzSAtRPDMwMc981LYeD31Pn48pnUOpyvVvnOhA+djr6 22 | kjxysSaDaqq+lemqXk/AcxISFp2AaljdK8eeDnSacULGjLQmdTG1Jpc6ko/kGbD4 23 | J2yeB4HQJ6cFsZTVicqORffqXV9rPoe+0BUDQweSs2/aTYsyiNOsWixkzdj5ZKFw 24 | xTEOwXBIyTt970TNJPOPRIVG5Fau5H+U/DnhrKVyLxlWYEVoYT35zAHOtlUZNR8E 25 | 7NPPsn2zTkaG5S68vZ/w8GGLQkRIl1fC89HEFYiIdPYNbNlyKsVlODQZ53zr39VS 26 | pBTP7igelastMizfIECzkSKU2i+/io6kV9PUQ2oq1mEeEdDBYi0TwGX5d9zXOf15 27 | yG+PIHzb1EGQRamRC89ij0jTS/uCwgdd8ibWVzekeC43/BiSAIuGWKFF9Sq9XBdb 28 | HEO0V8WoMxRDKe0S3FchXNWbMBcWBUVBFs5YXCmo0O+4KJNp+2XoLA7ayweW 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /examples/selfsigned.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDPnannEwyuOpG3 3 | Cn2gfqbtM5U2QhTZaiFRB+Fs+AF2mSNkme/oEEnJ5ScN8kdrJgYDtU82HcwRYIjM 4 | VSiT3x9JnMiHiqp9JOD7Zh00h8z9Y/zqMDr5he60uX50Fl9MtbQM1SwISWBNYWma 5 | LkAPaiqUyp0kqo6Hl7x7osGWm6RdxznKQ3MVtOmSlz31/uT/bMwF7XbTwSMdcyxx 6 | cPG4RN2VPJKj0W161TEu5zunprqDjvbb3r8dgNqqNhl7G0oJOllvhhcAb/4hxGo+ 7 | LSp9LLgEqxgiajQbqVMibbzO9AE6RT2GlbDka0p5aN2wX8avvzRAFjcB5QxfRwLi 8 | JT6Cirg0sKFEL11177Idk5QvtSA8A8O1BiMtn2lt+LemQlnjzv1giQMP8zRmHos+ 9 | Cy18b09E0EURd1v0iZrxmzE3xh56Wwxl+YcpxY3w8CcfYRMbFWxc9dS7PU+gjESH 10 | 8xZbHWUBKoXRtC0uwk/f1cZaJGSGDi92My/2WTj65nBnugOJeoYQFPN3Ea28Vg2M 11 | A5a2ym6Z20/0zAL6gjUhHpL9sbjmO3OSR6JbP+xbWeS2MWRKB28QI4EkBAz5Gw0S 12 | z/1ulBX5jx2LXZr/uKaxJBpyy+R/czCbXbvNhx0wlV+DzgBkLP4SYNoWp8705B2w 13 | s+1CEvpCmLF3cy2fVajEiGG1v5uVNQIDAQABAoICACSN+wM/fGUU1OEojLP8eMGc 14 | 6nGyMt+Q6yrMO2mnRQKvteaZn/75FzTgDv9KoD6CZF60xqydlHeeypdHiyx2BZk9 15 | bKVIyfncy2wYL543JuWafEZzlX6nkT7qxhQEeGUWPQxhYC5ZVQZq12AZMphENhka 16 | j46MJSpEkiAmqPUulEMat9cgBxxUTSfNT1CHv6QlcMq+Y8Sm5drik3mpzDWIkocb 17 | Mip7zk3pSY6bkgpTtdVCD77oujekn1uGyPe+90smpeaX8mbWUSV64sXtx+RgQko4 18 | Ibi1gFU6e/O85Jh/p9Otq0aOBqZBKcy0pQvP4TjCbp22C5tey83ev+g3bIkpiYMT 19 | 3JDJ5hF1qcdmuFbmFrXWn/4cdnSXRr+LGacOBMeIsJzgU7mIwsvOFqX89/oarRA/ 20 | aG5RrzhJyt1rHb+tVre8jcgLQnpferENdJ2/UWiSi5J1BhPBYoPUxDeqqABItD31 21 | n6Rn6UZAOOzkMzpsB1SLSTg/hbTR2Y6kvQC2MZBLssnf+fxOH1/q+iYh8AL/kj57 22 | oRRbN+Pk5WxAN8+NT/meGhgsjKTeo5GooIHPmDIHBWurLZ5tBqbEQrqQHYWaOLig 23 | 5tUhlZ/mD3/6kZQHjbmUwIjebRNeNmJhu0z8qhwpwVpPcUspUoHcDH84eWCDHC0H 24 | OHYWGW1swE4g30W09FsFAoIBAQD+pC3XP/l3yzOPX8/5W2uQWihWSm92+8USrZSm 25 | vbYz9jPb4vq36rRzLduxvkxrVeqF5ldykBYKWXXVJCirpBoTC5+eh1oTAzRGutm5 26 | +U2zbwtsDSvUmzI6MbH7xx8d2Beh0IgfWGIB3Veh2yxoPvo6QcUHR37I9KMrZsPQ 27 | 70hg9+zWAF0Sh0t3ecoBocRqkrk0Lw1DARGOA4uTNbZcDJQAzPDmSWxAlbU2MoRU 28 | HWjTN3WXmOrsIC7VrFcByaFXKxwSL0j1yMlBQyGg4bclYb/8fb3StWVWU2XBUIgS 29 | xqsFGNnXpGkmY9HQ1RJF+wvJUMsWjgXqwf4iTzGJ1TPrJSgLAoIBAQDQuUBafgII 30 | Jc8YHGCn4IDobE5yM9BKHu1M3YnthenoJFu2DYRCiQV9QMNUzF2XrqfGQwAqydQi 31 | 2Wfwqnh7tXoZbIRljEyDxfBI1YQmr9icFyoq7JIa+4f2AIuUl0AQk8wYvwELVPC/ 32 | fU/N3LMnVLjq5VajPt9blwSBeUpjGA8y0qJaQ2KV7knFQwtI7OrAeNFH8I/wkIRH 33 | vNy+/Sz0BcDHRRL0120/45DFNCuxyvs5EN/dAuvOfSJqndY1t/fAVoNg2wvAyns/ 34 | sbKQeexd/clTYGjqAMEU/sImtQEwldeCPvuCAhUwASylrcHWoHsCDb4pKcgiFsIN 35 | tPWgsdrbGD+/AoIBAQD2fVh+Z0gGBOYJIFcCatNJbWxkczNIutf+h5ZAfZ202NtE 36 | O4g0pfY9FCP4/1ub/xPAv8Lge8dKB2T/iDvyQiyXSQYe/6hahRyCZvbBhikHyzME 37 | Sg+mgwBwwpAmR47AZeAiW+iYZwagBXGBlNZ8ppGz+NxPeo6o2d5k8doVErs+Wl+g 38 | m8N8XwjXQ0YepEesXhD3CaDNvmgOzzG5syGuIuLVj4yVbndiYUiDiQz9G2bQJnwm 39 | 3fhxz4lmfqfObC5IYcuPcsQuX0kpamFQCY4umlusfs9T+xF4Kcxy/5BolHURvweI 40 | LXc3mSKOAuLoaOX03sdoMtxZbaWh8oTihkX2lgYXAoIBAGhhHCOk/FMixUwjdNq/ 41 | VPfmodxOuQ04JifYak+UNoNXG14RqGC1sT8QEh7oDK38M/7cJss/H41F98rNFW+Y 42 | M7VfJV67KNCFPkLONEY8jjCRDQ9mOzKvMzD82NC4Stt/bgO6EUWfdr3sZupmQlma 43 | 7tbZVdhRatWc0i4FgAPKVl9uIq7NIBImllHF03DmugcC5HX7gaAmRWCyvBnu9noa 44 | HmwIyRAUY5gdr5pPGsLQ5Y2GOM2H1nDu9zUmNaerloRjP1RCdsA1Aim6Lbg+oMvo 45 | TLQbdJwBQI3FUUaWIkAvzxRddt1vOTVGgRNhr5wrqRg/0yc2s9UIWIcORf/UscP7 46 | fnUCggEAPi88JcWuzlJmEUSJdgjRzdCnNh3ZyOUG8L8SIQaLs098hsJhR4cpHc6Q 47 | 2uXr7kuTkXlNh/vUZmfFMQJe+8Pg+fjUzom7oM4iOEGozJRyY9phjPWvUgE2UWfd 48 | Pa2nLqhP2UIplayvr6gGPYVlxSnaddZPMBB53W641nYxO1iuEHY25vdkAnhQU1K+ 49 | BfaR9E7Rc80AAhK+Fhs2xzoHUKJ/z1ciFtZylqWZNDu2jHSgBLawDljhCqLstJ3q 50 | vx7Qlq+j6dh68BVb60/R4RRsRy6pvUFbYcKPFiuEPK3EYr3Ascy7P2tn9Z8LdXie 51 | flYh3XPcmbDCBLfXFvforZR+CYzBlA== 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /examples/thread.capnp: -------------------------------------------------------------------------------- 1 | @0xf5745ea9c82baa3a; 2 | 3 | interface Example { 4 | interface StatusSubscriber { 5 | status @0 (value: Bool); 6 | # Call the function on the given parameters. 7 | } 8 | 9 | longRunning @0 () -> (value: Bool); 10 | subscribeStatus @1 (subscriber: StatusSubscriber); 11 | alive @2 () -> (value: Bool); 12 | } 13 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel", "pkgconfig", "cython>=3.0"] 3 | build-backend = "backend" 4 | backend-path = ["_custom_build"] 5 | 6 | [tool.pytest.ini_options] 7 | asyncio_mode = "auto" 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | jinja2 2 | black 3 | cython>=3 4 | flake8 5 | setuptools 6 | pkgconfig 7 | pytest 8 | sphinx 9 | sphinx-multiversion 10 | tox 11 | wheel 12 | -------------------------------------------------------------------------------- /scripts/capnp-json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | import sys 5 | import json 6 | import capnp 7 | 8 | 9 | def parse_args(): 10 | parser = argparse.ArgumentParser() 11 | parser.add_argument("command") 12 | parser.add_argument("schema_file") 13 | parser.add_argument("struct_name") 14 | parser.add_argument( 15 | "-d", 16 | "--defaults", 17 | help="include default values in json output", 18 | action="store_true", 19 | ) 20 | 21 | return parser.parse_args() 22 | 23 | 24 | def encode(schema_file, struct_name, **kwargs): 25 | schema = capnp.load(schema_file) 26 | 27 | struct_schema = getattr(schema, struct_name) 28 | 29 | struct_dict = json.load(sys.stdin) 30 | struct = struct_schema.from_dict(struct_dict) 31 | 32 | struct.write(sys.stdout) 33 | 34 | 35 | def decode(schema_file, struct_name, defaults): 36 | schema = capnp.load(schema_file) 37 | 38 | struct_schema = getattr(schema, struct_name) 39 | struct = struct_schema.read(sys.stdin) 40 | 41 | json.dump(struct.to_dict(defaults), sys.stdout) 42 | 43 | 44 | def main(): 45 | args = parse_args() 46 | 47 | command = args.command 48 | kwargs = vars(args) 49 | del kwargs["command"] 50 | 51 | globals()[command]( 52 | **kwargs 53 | ) # hacky way to get defined functions, and call function with name=command 54 | 55 | 56 | main() 57 | -------------------------------------------------------------------------------- /scripts/capnp_test_pycapnp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | import capnp 6 | 7 | capnp.add_import_hook( 8 | [os.getcwd(), "/usr/local/include/"] 9 | ) # change this to be auto-detected? 10 | 11 | import test_capnp # noqa: E402 12 | 13 | 14 | def decode(name): 15 | class_name = name[0].upper() + name[1:] 16 | with getattr(test_capnp, class_name).from_bytes(sys.stdin.read()) as msg: 17 | print(msg._short_str()) 18 | 19 | 20 | def encode(name): 21 | val = getattr(test_capnp, name) 22 | class_name = name[0].upper() + name[1:] 23 | message = getattr(test_capnp, class_name).from_dict(val.to_dict()) 24 | print(message.to_bytes()) 25 | 26 | 27 | if sys.argv[1] == "decode": 28 | decode(sys.argv[2]) 29 | else: 30 | encode(sys.argv[2]) 31 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description_file = README.md 3 | license_files = LICENSE.md 4 | -------------------------------------------------------------------------------- /test/addressbook with spaces.capnp: -------------------------------------------------------------------------------- 1 | @0xc39aee9191aedcf3; 2 | 3 | const qux :UInt32 = 123; 4 | 5 | struct Person { 6 | id @0 :UInt32; 7 | name @1 :Text; 8 | email @2 :Text; 9 | phones @3 :List(PhoneNumber); 10 | 11 | struct PhoneNumber { 12 | number @0 :Text; 13 | type @1 :Type; 14 | 15 | enum Type { 16 | mobile @0; 17 | home @1; 18 | work @2; 19 | } 20 | } 21 | 22 | employment :union { 23 | unemployed @4 :Void; 24 | employer @5 :Employer; 25 | school @6 :Text; 26 | selfEmployed @7 :Void; 27 | # We assume that a person is only one of these. 28 | } 29 | } 30 | 31 | struct Employer { 32 | name @0 :Text; 33 | boss @1 :Person; 34 | } 35 | 36 | struct AddressBook { 37 | people @0 :List(Person); 38 | } 39 | 40 | -------------------------------------------------------------------------------- /test/addressbook-with-dashes.capnp: -------------------------------------------------------------------------------- 1 | @0xd33206731939e03b; 2 | 3 | const qux :UInt32 = 123; 4 | 5 | struct Person { 6 | id @0 :UInt32; 7 | name @1 :Text; 8 | email @2 :Text; 9 | phones @3 :List(PhoneNumber); 10 | 11 | struct PhoneNumber { 12 | number @0 :Text; 13 | type @1 :Type; 14 | 15 | enum Type { 16 | mobile @0; 17 | home @1; 18 | work @2; 19 | } 20 | } 21 | 22 | employment :union { 23 | unemployed @4 :Void; 24 | employer @5 :Employer; 25 | school @6 :Text; 26 | selfEmployed @7 :Void; 27 | # We assume that a person is only one of these. 28 | } 29 | } 30 | 31 | struct Employer { 32 | name @0 :Text; 33 | boss @1 :Person; 34 | } 35 | 36 | struct AddressBook { 37 | people @0 :List(Person); 38 | } 39 | 40 | -------------------------------------------------------------------------------- /test/addressbook.capnp: -------------------------------------------------------------------------------- 1 | @0x934efea7f017fff0; 2 | 3 | const qux :UInt32 = 123; 4 | 5 | struct Person { 6 | id @0 :UInt32; 7 | name @1 :Text; 8 | email @2 :Text; 9 | phones @3 :List(PhoneNumber); 10 | 11 | struct PhoneNumber { 12 | number @0 :Text; 13 | type @1 :Type; 14 | 15 | enum Type { 16 | mobile @0; 17 | home @1; 18 | work @2; 19 | } 20 | } 21 | 22 | employment :union { 23 | unemployed @4 :Void; 24 | employer @5 :Employer; 25 | school @6 :Text; 26 | selfEmployed @7 :Void; 27 | # We assume that a person is only one of these. 28 | } 29 | } 30 | 31 | struct Employer { 32 | name @0 :Text; 33 | boss @1 :Person; 34 | } 35 | 36 | struct AddressBook { 37 | people @0 :List(Person); 38 | } 39 | 40 | struct NestedList { 41 | list @0 :List(List(Int32)); 42 | } 43 | -------------------------------------------------------------------------------- /test/all-types.binary: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capnproto/pycapnp/a89eb0dee6c67049bf2f6b875e51dd3f71a6abd1/test/all-types.binary -------------------------------------------------------------------------------- /test/all-types.packed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capnproto/pycapnp/a89eb0dee6c67049bf2f6b875e51dd3f71a6abd1/test/all-types.packed -------------------------------------------------------------------------------- /test/all-types.txt: -------------------------------------------------------------------------------- 1 | ( voidField = void, 2 | boolField = true, 3 | int8Field = -123, 4 | int16Field = -12345, 5 | int32Field = -12345678, 6 | int64Field = -123456789012345, 7 | uInt8Field = 234, 8 | uInt16Field = 45678, 9 | uInt32Field = 3456789012, 10 | uInt64Field = 12345678901234567890, 11 | float32Field = 1234.5, 12 | float64Field = -1.23e47, 13 | textField = "foo", 14 | dataField = "bar", 15 | structField = ( 16 | voidField = void, 17 | boolField = true, 18 | int8Field = -12, 19 | int16Field = 3456, 20 | int32Field = -78901234, 21 | int64Field = 56789012345678, 22 | uInt8Field = 90, 23 | uInt16Field = 1234, 24 | uInt32Field = 56789012, 25 | uInt64Field = 345678901234567890, 26 | float32Field = -1.25e-10, 27 | float64Field = 345, 28 | textField = "☃", 29 | dataField = "qux", 30 | structField = ( 31 | voidField = void, 32 | boolField = false, 33 | int8Field = 0, 34 | int16Field = 0, 35 | int32Field = 0, 36 | int64Field = 0, 37 | uInt8Field = 0, 38 | uInt16Field = 0, 39 | uInt32Field = 0, 40 | uInt64Field = 0, 41 | float32Field = 0, 42 | float64Field = 0, 43 | textField = "nested", 44 | structField = ( 45 | voidField = void, 46 | boolField = false, 47 | int8Field = 0, 48 | int16Field = 0, 49 | int32Field = 0, 50 | int64Field = 0, 51 | uInt8Field = 0, 52 | uInt16Field = 0, 53 | uInt32Field = 0, 54 | uInt64Field = 0, 55 | float32Field = 0, 56 | float64Field = 0, 57 | textField = "really nested", 58 | enumField = foo, 59 | interfaceField = void ), 60 | enumField = foo, 61 | interfaceField = void ), 62 | enumField = baz, 63 | interfaceField = void, 64 | voidList = [void, void, void], 65 | boolList = [false, true, false, true, true], 66 | int8List = [12, -34, -128, 127], 67 | int16List = [1234, -5678, -32768, 32767], 68 | int32List = [12345678, -90123456, -2147483648, 2147483647], 69 | int64List = [123456789012345, -678901234567890, -9223372036854775808, 9223372036854775807], 70 | uInt8List = [12, 34, 0, 255], 71 | uInt16List = [1234, 5678, 0, 65535], 72 | uInt32List = [12345678, 90123456, 0, 4294967295], 73 | uInt64List = [123456789012345, 678901234567890, 0, 18446744073709551615], 74 | float32List = [0, 1234567, 1e37, -1e37, 1e-37, -1e-37], 75 | float64List = [0, 123456789012345, 1e306, -1e306, 1e-306, -1e-306], 76 | textList = ["quux", "corge", "grault"], 77 | dataList = ["garply", "waldo", "fred"], 78 | structList = [ 79 | ( voidField = void, 80 | boolField = false, 81 | int8Field = 0, 82 | int16Field = 0, 83 | int32Field = 0, 84 | int64Field = 0, 85 | uInt8Field = 0, 86 | uInt16Field = 0, 87 | uInt32Field = 0, 88 | uInt64Field = 0, 89 | float32Field = 0, 90 | float64Field = 0, 91 | textField = "x structlist 1", 92 | enumField = foo, 93 | interfaceField = void ), 94 | ( voidField = void, 95 | boolField = false, 96 | int8Field = 0, 97 | int16Field = 0, 98 | int32Field = 0, 99 | int64Field = 0, 100 | uInt8Field = 0, 101 | uInt16Field = 0, 102 | uInt32Field = 0, 103 | uInt64Field = 0, 104 | float32Field = 0, 105 | float64Field = 0, 106 | textField = "x structlist 2", 107 | enumField = foo, 108 | interfaceField = void ), 109 | ( voidField = void, 110 | boolField = false, 111 | int8Field = 0, 112 | int16Field = 0, 113 | int32Field = 0, 114 | int64Field = 0, 115 | uInt8Field = 0, 116 | uInt16Field = 0, 117 | uInt32Field = 0, 118 | uInt64Field = 0, 119 | float32Field = 0, 120 | float64Field = 0, 121 | textField = "x structlist 3", 122 | enumField = foo, 123 | interfaceField = void ) ], 124 | enumList = [qux, bar, grault] ), 125 | enumField = corge, 126 | interfaceField = void, 127 | voidList = [void, void, void, void, void, void], 128 | boolList = [true, false, false, true], 129 | int8List = [111, -111], 130 | int16List = [11111, -11111], 131 | int32List = [111111111, -111111111], 132 | int64List = [1111111111111111111, -1111111111111111111], 133 | uInt8List = [111, 222], 134 | uInt16List = [33333, 44444], 135 | uInt32List = [3333333333], 136 | uInt64List = [11111111111111111111], 137 | float32List = [5555.5, inf, -inf, nan], 138 | float64List = [7777.75, inf, -inf, nan], 139 | textList = ["plugh", "xyzzy", "thud"], 140 | dataList = ["oops", "exhausted", "rfc3092"], 141 | structList = [ 142 | ( voidField = void, 143 | boolField = false, 144 | int8Field = 0, 145 | int16Field = 0, 146 | int32Field = 0, 147 | int64Field = 0, 148 | uInt8Field = 0, 149 | uInt16Field = 0, 150 | uInt32Field = 0, 151 | uInt64Field = 0, 152 | float32Field = 0, 153 | float64Field = 0, 154 | textField = "structlist 1", 155 | enumField = foo, 156 | interfaceField = void ), 157 | ( voidField = void, 158 | boolField = false, 159 | int8Field = 0, 160 | int16Field = 0, 161 | int32Field = 0, 162 | int64Field = 0, 163 | uInt8Field = 0, 164 | uInt16Field = 0, 165 | uInt32Field = 0, 166 | uInt64Field = 0, 167 | float32Field = 0, 168 | float64Field = 0, 169 | textField = "structlist 2", 170 | enumField = foo, 171 | interfaceField = void ), 172 | ( voidField = void, 173 | boolField = false, 174 | int8Field = 0, 175 | int16Field = 0, 176 | int32Field = 0, 177 | int64Field = 0, 178 | uInt8Field = 0, 179 | uInt16Field = 0, 180 | uInt32Field = 0, 181 | uInt64Field = 0, 182 | float32Field = 0, 183 | float64Field = 0, 184 | textField = "structlist 3", 185 | enumField = foo, 186 | interfaceField = void ) ], 187 | enumList = [foo, garply] ) 188 | -------------------------------------------------------------------------------- /test/all_types.capnp: -------------------------------------------------------------------------------- 1 | # This is copied from test.capnp in the Cap'n Proto C++ code. 2 | 3 | @0xcaa67a26d16950d9; 4 | 5 | enum TestEnum { 6 | foo @0; 7 | bar @1; 8 | baz @2; 9 | qux @3; 10 | quux @4; 11 | corge @5; 12 | grault @6; 13 | garply @7; 14 | } 15 | 16 | struct TestAllTypes { 17 | voidField @0 : Void; 18 | boolField @1 : Bool; 19 | int8Field @2 : Int8; 20 | int16Field @3 : Int16; 21 | int32Field @4 : Int32; 22 | int64Field @5 : Int64; 23 | uInt8Field @6 : UInt8; 24 | uInt16Field @7 : UInt16; 25 | uInt32Field @8 : UInt32; 26 | uInt64Field @9 : UInt64; 27 | float32Field @10 : Float32; 28 | float64Field @11 : Float64; 29 | textField @12 : Text; 30 | dataField @13 : Data; 31 | structField @14 : TestAllTypes; 32 | enumField @15 : TestEnum; 33 | interfaceField @16 : Void; # TODO 34 | 35 | voidList @17 : List(Void); 36 | boolList @18 : List(Bool); 37 | int8List @19 : List(Int8); 38 | int16List @20 : List(Int16); 39 | int32List @21 : List(Int32); 40 | int64List @22 : List(Int64); 41 | uInt8List @23 : List(UInt8); 42 | uInt16List @24 : List(UInt16); 43 | uInt32List @25 : List(UInt32); 44 | uInt64List @26 : List(UInt64); 45 | float32List @27 : List(Float32); 46 | float64List @28 : List(Float64); 47 | textList @29 : List(Text); 48 | dataList @30 : List(Data); 49 | structList @31 : List(TestAllTypes); 50 | enumList @32 : List(TestEnum); 51 | interfaceList @33 : List(Void); # TODO 52 | } 53 | 54 | struct UnionAllTypes { 55 | union { 56 | unionStructField1 @0 : TestAllTypes; 57 | unionStructField2 @1 : TestAllTypes; 58 | } 59 | } 60 | 61 | struct GroupedUnionAllTypes { 62 | union { 63 | g1 :group { 64 | unionStructField1 @0 : TestAllTypes; 65 | } 66 | g2 :group { 67 | unionStructField2 @1 : TestAllTypes; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/annotations.capnp: -------------------------------------------------------------------------------- 1 | @0xfb9a160831eee9bb; 2 | 3 | struct AnnotationStruct { 4 | test @0: Int32; 5 | } 6 | 7 | annotation test1(*): Text; 8 | annotation test2(*): AnnotationStruct; 9 | annotation test3(*): List(AnnotationStruct); 10 | annotation test4(*): List(UInt16); 11 | 12 | $test1("TestFile"); 13 | 14 | struct TestAnnotationOne $test1("Test") { } 15 | struct TestAnnotationTwo $test2(test = 100) { } 16 | struct TestAnnotationThree $test3([(test=100), (test=101)]) { } 17 | struct TestAnnotationFour $test4([200, 201]) { } 18 | -------------------------------------------------------------------------------- /test/bar.capnp: -------------------------------------------------------------------------------- 1 | @0x84ed73ce30ad1d3e; 2 | 3 | using Foo = import "foo.capnp"; 4 | 5 | struct Bar { 6 | id @0 :UInt32; 7 | foo @1 :Foo.Foo; 8 | } 9 | -------------------------------------------------------------------------------- /test/foo.capnp: -------------------------------------------------------------------------------- 1 | @0x84ed73ce30ad1d3f; 2 | 3 | struct Foo { 4 | id @0 :UInt32; 5 | name @1 :Text; 6 | } 7 | 8 | 9 | struct Baz{ 10 | text @0 :Text; 11 | qux @1 :Qux; 12 | } 13 | 14 | struct Qux{ 15 | id @0 :UInt64; 16 | } 17 | 18 | interface Wrapper { 19 | wrapped @0 (object :AnyPointer); 20 | } 21 | -------------------------------------------------------------------------------- /test/schemas/child.capnp: -------------------------------------------------------------------------------- 1 | @0x9afc0f7513269df3; 2 | 3 | struct Child { 4 | name @0 :Text; 5 | } 6 | -------------------------------------------------------------------------------- /test/schemas/parent.capnp: -------------------------------------------------------------------------------- 1 | @0x95c41c96183b9c2f; 2 | 3 | using import "/schemas/child.capnp".Child; 4 | 5 | struct Parent { 6 | child @0 :List(Child); 7 | } 8 | -------------------------------------------------------------------------------- /test/test_capability.capnp: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013, Kenton Varda 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright notice, 10 | # this list of conditions and the following disclaimer in the documentation 11 | # and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | @0xd508eefec2dc42b8; 25 | 26 | interface TestInterface { 27 | foo @0 (i :UInt32, j :Bool) -> (x: Text); 28 | bar @1 () -> (); 29 | buz @2 (i: TestSturdyRefHostId) -> (x: Text); 30 | bam @3 (i :UInt32, j :Bool) -> (x: Text, i:UInt32); 31 | bak1 @4 () -> (i:List(UInt32)); 32 | bak2 @5 (i:List(UInt32)) -> (); 33 | # baz @2 (s: TestAllTypes); 34 | } 35 | 36 | interface TestExtends extends(TestInterface) { 37 | qux @0 (); 38 | } 39 | 40 | interface TestPipeline { 41 | getCap @0 (n: UInt32, inCap :TestInterface) -> (s: Text, outBox :Box); 42 | testPointers @1 (cap :TestInterface, obj :AnyPointer, list :List(TestInterface)) -> (); 43 | 44 | struct Box { 45 | cap @0 :TestInterface; 46 | } 47 | } 48 | 49 | struct TestSturdyRefHostId { 50 | host @0 :Text; 51 | } 52 | 53 | struct TestSturdyRefObjectId { 54 | tag @0 :Tag; 55 | enum Tag { 56 | testInterface @0; 57 | testExtends @1; 58 | testPipeline @2; 59 | } 60 | } 61 | 62 | interface TestCallOrder { 63 | getCallSequence @0 (expected: UInt32) -> (n: UInt32); 64 | # First call returns 0, next returns 1, ... 65 | # 66 | # The input `expected` is ignored but useful for disambiguating debug logs. 67 | } 68 | 69 | interface TestTailCallee { 70 | struct TailResult { 71 | i @0 :UInt32; 72 | t @1 :Text; 73 | c @2 :TestCallOrder; 74 | } 75 | 76 | foo @0 (i :Int32, t :Text) -> TailResult; 77 | } 78 | 79 | interface TestTailCaller { 80 | foo @0 (i :Int32, callee :TestTailCallee) -> TestTailCallee.TailResult; 81 | } 82 | 83 | interface TestPassedCap { 84 | foo @0 (cap :TestInterface) -> (x: Text); 85 | } 86 | 87 | interface TestStructArg { 88 | bar @0 BarParams -> (c: Text); 89 | } 90 | struct BarParams { 91 | a @0 :Text; 92 | b @1 :Int32; 93 | } 94 | 95 | interface TestGeneric(MyObject) { 96 | foo @0 (a :MyObject) -> (b: Text); 97 | } 98 | -------------------------------------------------------------------------------- /test/test_capability_context.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import capnp 4 | import test_capability_capnp as capability 5 | 6 | 7 | @pytest.fixture(autouse=True) 8 | async def kj_loop(): 9 | async with capnp.kj_loop(): 10 | yield 11 | 12 | 13 | class Server(capability.TestInterface.Server): 14 | def __init__(self, val=1): 15 | self.val = val 16 | 17 | async def foo_context(self, context): 18 | extra = 0 19 | if context.params.j: 20 | extra = 1 21 | context.results.x = str(context.params.i * 5 + extra + self.val) 22 | 23 | async def buz_context(self, context): 24 | context.results.x = context.params.i.host + "_test" 25 | 26 | 27 | class PipelineServer(capability.TestPipeline.Server): 28 | async def getCap_context(self, context): 29 | response = await context.params.inCap.foo(i=context.params.n) 30 | context.results.s = response.x + "_foo" 31 | context.results.outBox.cap = Server(100) 32 | 33 | 34 | async def test_client_context(): 35 | client = capability.TestInterface._new_client(Server()) 36 | 37 | req = client._request("foo") 38 | req.i = 5 39 | 40 | remote = req.send() 41 | response = await remote 42 | 43 | assert response.x == "26" 44 | 45 | req = client.foo_request() 46 | req.i = 5 47 | 48 | remote = req.send() 49 | response = await remote 50 | 51 | assert response.x == "26" 52 | 53 | with pytest.raises(AttributeError): 54 | client.foo2_request() 55 | 56 | req = client.foo_request() 57 | 58 | with pytest.raises(Exception): 59 | req.i = "foo" 60 | 61 | req = client.foo_request() 62 | 63 | with pytest.raises(AttributeError): 64 | req.baz = 1 65 | 66 | 67 | async def test_simple_client_context(): 68 | client = capability.TestInterface._new_client(Server()) 69 | 70 | remote = client._send("foo", i=5) 71 | response = await remote 72 | 73 | assert response.x == "26" 74 | 75 | remote = client.foo(i=5) 76 | response = await remote 77 | 78 | assert response.x == "26" 79 | 80 | remote = client.foo(i=5, j=True) 81 | response = await remote 82 | 83 | assert response.x == "27" 84 | 85 | remote = client.foo(5) 86 | response = await remote 87 | 88 | assert response.x == "26" 89 | 90 | remote = client.foo(5, True) 91 | response = await remote 92 | 93 | assert response.x == "27" 94 | 95 | remote = client.foo(5, j=True) 96 | response = await remote 97 | 98 | assert response.x == "27" 99 | 100 | remote = client.buz(capability.TestSturdyRefHostId.new_message(host="localhost")) 101 | response = await remote 102 | 103 | assert response.x == "localhost_test" 104 | 105 | with pytest.raises(Exception): 106 | remote = client.foo(5, 10) 107 | 108 | with pytest.raises(Exception): 109 | remote = client.foo(5, True, 100) 110 | 111 | with pytest.raises(Exception): 112 | remote = client.foo(i="foo") 113 | 114 | with pytest.raises(AttributeError): 115 | remote = client.foo2(i=5) 116 | 117 | with pytest.raises(Exception): 118 | remote = client.foo(baz=5) 119 | 120 | 121 | async def test_pipeline_context(): 122 | client = capability.TestPipeline._new_client(PipelineServer()) 123 | foo_client = capability.TestInterface._new_client(Server()) 124 | 125 | remote = client.getCap(n=5, inCap=foo_client) 126 | 127 | outCap = remote.outBox.cap 128 | pipelinePromise = outCap.foo(i=10) 129 | 130 | response = await pipelinePromise 131 | assert response.x == "150" 132 | 133 | response = await remote 134 | assert response.s == "26_foo" 135 | 136 | 137 | class BadServer(capability.TestInterface.Server): 138 | def __init__(self, val=1): 139 | self.val = val 140 | 141 | async def foo_context(self, context): 142 | context.results.x = str(context.params.i * 5 + self.val) 143 | context.results.x2 = 5 # raises exception 144 | 145 | 146 | async def test_exception_client_context(): 147 | client = capability.TestInterface._new_client(BadServer()) 148 | 149 | remote = client._send("foo", i=5) 150 | with pytest.raises(capnp.KjException): 151 | await remote 152 | 153 | 154 | class BadPipelineServer(capability.TestPipeline.Server): 155 | async def getCap_context(self, context): 156 | try: 157 | await context.params.inCap.foo(i=context.params.n) 158 | except capnp.KjException: 159 | raise Exception("test was a success") 160 | 161 | 162 | async def test_exception_chain_context(): 163 | client = capability.TestPipeline._new_client(BadPipelineServer()) 164 | foo_client = capability.TestInterface._new_client(BadServer()) 165 | 166 | remote = client.getCap(n=5, inCap=foo_client) 167 | 168 | try: 169 | await remote 170 | except Exception as e: 171 | assert "test was a success" in str(e) 172 | 173 | 174 | async def test_pipeline_exception_context(): 175 | client = capability.TestPipeline._new_client(BadPipelineServer()) 176 | foo_client = capability.TestInterface._new_client(BadServer()) 177 | 178 | remote = client.getCap(n=5, inCap=foo_client) 179 | 180 | outCap = remote.outBox.cap 181 | pipelinePromise = outCap.foo(i=10) 182 | 183 | with pytest.raises(Exception): 184 | await pipelinePromise 185 | 186 | with pytest.raises(Exception): 187 | await remote 188 | 189 | 190 | async def test_casting_context(): 191 | client = capability.TestExtends._new_client(Server()) 192 | client2 = client.upcast(capability.TestInterface) 193 | _ = client2.cast_as(capability.TestInterface) 194 | 195 | with pytest.raises(Exception): 196 | client.upcast(capability.TestPipeline) 197 | 198 | 199 | class TailCallOrder(capability.TestCallOrder.Server): 200 | def __init__(self): 201 | self.count = -1 202 | 203 | async def getCallSequence_context(self, context): 204 | self.count += 1 205 | context.results.n = self.count 206 | 207 | 208 | class TailCaller(capability.TestTailCaller.Server): 209 | def __init__(self): 210 | self.count = 0 211 | 212 | async def foo_context(self, context): 213 | self.count += 1 214 | 215 | tail = context.params.callee.foo_request( 216 | i=context.params.i, t="from TailCaller" 217 | ) 218 | await context.tail_call(tail) 219 | 220 | 221 | class TailCallee(capability.TestTailCallee.Server): 222 | def __init__(self): 223 | self.count = 0 224 | 225 | async def foo_context(self, context): 226 | self.count += 1 227 | 228 | results = context.results 229 | results.i = context.params.i 230 | results.t = context.params.t 231 | results.c = TailCallOrder() 232 | 233 | 234 | async def test_tail_call(): 235 | callee_server = TailCallee() 236 | caller_server = TailCaller() 237 | 238 | callee = capability.TestTailCallee._new_client(callee_server) 239 | caller = capability.TestTailCaller._new_client(caller_server) 240 | 241 | promise = caller.foo(i=456, callee=callee) 242 | dependent_call1 = promise.c.getCallSequence() 243 | 244 | response = await promise 245 | 246 | assert response.i == 456 247 | assert response.i == 456 248 | 249 | dependent_call2 = response.c.getCallSequence() 250 | dependent_call3 = response.c.getCallSequence() 251 | 252 | result = await dependent_call1 253 | assert result.n == 0 254 | result = await dependent_call2 255 | assert result.n == 1 256 | result = await dependent_call3 257 | assert result.n == 2 258 | 259 | assert callee_server.count == 1 260 | assert caller_server.count == 1 261 | -------------------------------------------------------------------------------- /test/test_examples.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | import socket 4 | import subprocess 5 | import sys 6 | import time 7 | 8 | examples_dir = os.path.join(os.path.dirname(__file__), "..", "examples") 9 | hostname = "localhost" 10 | 11 | 12 | processes = [] 13 | 14 | 15 | @pytest.fixture 16 | def cleanup(): 17 | yield 18 | for p in processes: 19 | p.kill() 20 | 21 | 22 | def run_subprocesses( 23 | address, server, client, wildcard_server=False, ipv4_force=True 24 | ): # noqa 25 | server_attempt = 0 26 | server_attempts = 2 27 | done = False 28 | addr, port = address.split(":") 29 | c_address = address 30 | s_address = address 31 | while not done: 32 | assert server_attempt < server_attempts, "Failed {} server attempts".format( 33 | server_attempts 34 | ) 35 | server_attempt += 1 36 | 37 | # Force ipv4 for tests (known issues on GitHub Actions with IPv6 for some targets) 38 | if "unix" not in addr and ipv4_force: 39 | addr = socket.gethostbyname(addr) 40 | c_address = "{}:{}".format(addr, port) 41 | s_address = c_address 42 | if wildcard_server: 43 | s_address = "*:{}".format(port) # Use wildcard address for server 44 | print("Forcing ipv4 -> {} => {} {}".format(address, c_address, s_address)) 45 | 46 | # Start server 47 | cmd = [sys.executable, os.path.join(examples_dir, server), s_address] 48 | serverp = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr) 49 | print("Server started (Attempt #{})".format(server_attempt)) 50 | processes.append(serverp) 51 | retries = 300 52 | # Loop until we have a socket connection to the server (with timeout) 53 | while True: 54 | try: 55 | if "unix" in address: 56 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 57 | result = sock.connect_ex(port) 58 | if result == 0: 59 | break 60 | else: 61 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 62 | result = sock.connect_ex((addr, int(port))) 63 | if result == 0: 64 | break 65 | sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 66 | result = sock.connect_ex((addr, int(port))) 67 | if result == 0: 68 | break 69 | except socket.gaierror as err: 70 | print("gaierror: {}".format(err)) 71 | # Give the server some small amount of time to start listening 72 | time.sleep(0.1) 73 | retries -= 1 74 | if retries == 0: 75 | serverp.kill() 76 | print("Timed out waiting for server to start") 77 | break 78 | 79 | if serverp.poll() is not None: 80 | print("Server exited prematurely: {}".format(serverp.returncode)) 81 | break 82 | 83 | # 3 tries per server try 84 | client_attempt = 0 85 | client_attempts = 3 86 | while not done: 87 | if client_attempt >= client_attempts: 88 | print("Failed {} client attempts".format(client_attempts)) 89 | break 90 | client_attempt += 1 91 | 92 | # Start client 93 | cmd = [sys.executable, os.path.join(examples_dir, client), c_address] 94 | clientp = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr) 95 | print("Client started (Attempt #{})".format(client_attempt)) 96 | processes.append(clientp) 97 | 98 | retries = 30 * 10 99 | # Loop until the client is finished (with timeout) 100 | while True: 101 | if clientp.poll() == 0: 102 | done = True 103 | break 104 | 105 | if clientp.poll() is not None: 106 | print("Client exited prematurely: {}".format(clientp.returncode)) 107 | break 108 | time.sleep(0.1) 109 | retries -= 1 110 | if retries == 0: 111 | print("Timed out waiting for client to finish") 112 | clientp.kill() 113 | break 114 | 115 | serverp.kill() 116 | 117 | serverp.kill() 118 | 119 | 120 | def test_async_calculator_example(unused_tcp_port, cleanup): 121 | address = "{}:{}".format(hostname, unused_tcp_port) 122 | server = "async_calculator_server.py" 123 | client = "async_calculator_client.py" 124 | run_subprocesses(address, server, client) 125 | 126 | 127 | def test_addressbook_example(cleanup): 128 | proc = subprocess.Popen( 129 | [sys.executable, os.path.join(examples_dir, "addressbook.py")] 130 | ) 131 | ret = proc.wait() 132 | assert ret == 0 133 | 134 | 135 | def test_async_example(unused_tcp_port, cleanup): 136 | address = "{}:{}".format(hostname, unused_tcp_port) 137 | server = "async_server.py" 138 | client = "async_client.py" 139 | run_subprocesses(address, server, client) 140 | 141 | 142 | def test_ssl_async_example(unused_tcp_port, cleanup): 143 | address = "{}:{}".format(hostname, unused_tcp_port) 144 | server = "async_ssl_server.py" 145 | client = "async_ssl_client.py" 146 | run_subprocesses(address, server, client, ipv4_force=False) 147 | 148 | 149 | def test_ssl_reconnecting_async_example(unused_tcp_port, cleanup): 150 | address = "{}:{}".format(hostname, unused_tcp_port) 151 | server = "async_ssl_server.py" 152 | client = "async_reconnecting_ssl_client.py" 153 | run_subprocesses(address, server, client, ipv4_force=False) 154 | 155 | 156 | def test_async_ssl_calculator_example(unused_tcp_port, cleanup): 157 | address = "{}:{}".format(hostname, unused_tcp_port) 158 | server = "async_ssl_calculator_server.py" 159 | client = "async_ssl_calculator_client.py" 160 | run_subprocesses(address, server, client, ipv4_force=False) 161 | 162 | 163 | def test_async_socket_message_example(unused_tcp_port, cleanup): 164 | address = "{}:{}".format(hostname, unused_tcp_port) 165 | server = "async_socket_message_server.py" 166 | client = "async_socket_message_client.py" 167 | run_subprocesses(address, server, client) 168 | -------------------------------------------------------------------------------- /test/test_large_read.capnp: -------------------------------------------------------------------------------- 1 | @0x86dbb3b256f5d2af; 2 | 3 | struct Row { 4 | values @0 :List(Int32); 5 | } 6 | 7 | struct MultiArray { 8 | rows @0 :List(Row); 9 | } 10 | 11 | struct Msg { 12 | data @0 :List(UInt8); 13 | } 14 | -------------------------------------------------------------------------------- /test/test_large_read.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import tempfile 4 | 5 | import pytest 6 | 7 | import capnp 8 | 9 | this_dir = os.path.dirname(__file__) 10 | 11 | 12 | @pytest.fixture 13 | def test_capnp(): 14 | return capnp.load(os.path.join(this_dir, "test_large_read.capnp")) 15 | 16 | 17 | def test_large_read(test_capnp): 18 | f = tempfile.TemporaryFile() 19 | 20 | array = test_capnp.MultiArray.new_message() 21 | 22 | row = array.init("rows", 1)[0] 23 | values = row.init("values", 10000) 24 | for i in range(len(values)): 25 | values[i] = i 26 | 27 | array.write_packed(f) 28 | f.seek(0) 29 | 30 | array = test_capnp.MultiArray.read_packed(f) 31 | del f 32 | assert array.rows[0].values[9000] == 9000 33 | 34 | 35 | def test_large_read_multiple(test_capnp): 36 | f = tempfile.TemporaryFile() 37 | msg1 = test_capnp.Msg.new_message() 38 | msg1.data = [0x41] * 8192 39 | msg1.write(f) 40 | msg2 = test_capnp.Msg.new_message() 41 | msg2.write(f) 42 | f.seek(0) 43 | 44 | for m in test_capnp.Msg.read_multiple(f): 45 | pass 46 | 47 | 48 | def get_two_adjacent_messages(test_capnp): 49 | msg1 = test_capnp.Msg.new_message() 50 | msg1.data = [0x41] * 8192 51 | m1 = msg1.to_bytes() 52 | msg2 = test_capnp.Msg.new_message() 53 | m2 = msg2.to_bytes() 54 | 55 | return m1 + m2 56 | 57 | 58 | def test_large_read_multiple_bytes(test_capnp): 59 | data = get_two_adjacent_messages(test_capnp) 60 | for m in test_capnp.Msg.read_multiple_bytes(data): 61 | pass 62 | 63 | with pytest.raises(capnp.KjException): 64 | data = get_two_adjacent_messages(test_capnp)[:-1] 65 | for m in test_capnp.Msg.read_multiple_bytes(data): 66 | pass 67 | 68 | with pytest.raises(capnp.KjException): 69 | data = get_two_adjacent_messages(test_capnp) + b" " 70 | for m in test_capnp.Msg.read_multiple_bytes(data): 71 | pass 72 | 73 | 74 | @pytest.mark.skipif( 75 | platform.python_implementation() == "PyPy", 76 | reason="PyPy memoryview support is limited", 77 | ) 78 | def test_large_read_mutltiple_bytes_memoryview(test_capnp): 79 | data = get_two_adjacent_messages(test_capnp) 80 | for m in test_capnp.Msg.read_multiple_bytes(memoryview(data)): 81 | pass 82 | 83 | with pytest.raises(capnp.KjException): 84 | data = get_two_adjacent_messages(test_capnp)[:-1] 85 | for m in test_capnp.Msg.read_multiple_bytes(memoryview(data)): 86 | pass 87 | 88 | with pytest.raises(capnp.KjException): 89 | data = get_two_adjacent_messages(test_capnp) + b" " 90 | for m in test_capnp.Msg.read_multiple_bytes(memoryview(data)): 91 | pass 92 | -------------------------------------------------------------------------------- /test/test_load.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import capnp 3 | import os 4 | import sys 5 | 6 | this_dir = os.path.dirname(__file__) 7 | 8 | 9 | @pytest.fixture 10 | def addressbook(): 11 | return capnp.load(os.path.join(this_dir, "addressbook.capnp")) 12 | 13 | 14 | @pytest.fixture 15 | def foo(): 16 | return capnp.load(os.path.join(this_dir, "foo.capnp")) 17 | 18 | 19 | @pytest.fixture 20 | def bar(): 21 | return capnp.load(os.path.join(this_dir, "bar.capnp")) 22 | 23 | 24 | def test_basic_load(): 25 | capnp.load(os.path.join(this_dir, "addressbook.capnp")) 26 | 27 | 28 | def test_constants(addressbook): 29 | assert addressbook.qux == 123 30 | 31 | 32 | def test_classes(addressbook): 33 | assert addressbook.AddressBook 34 | assert addressbook.Person 35 | 36 | 37 | def test_import(foo, bar): 38 | m = capnp._MallocMessageBuilder() 39 | foo = m.init_root(foo.Foo) 40 | m2 = capnp._MallocMessageBuilder() 41 | bar = m2.init_root(bar.Bar) 42 | 43 | foo.name = "foo" 44 | bar.foo = foo 45 | 46 | assert bar.foo.name == "foo" 47 | 48 | 49 | def test_failed_import(): 50 | s = capnp.SchemaParser() 51 | s2 = capnp.SchemaParser() 52 | 53 | foo = s.load(os.path.join(this_dir, "foo.capnp")) 54 | bar = s2.load(os.path.join(this_dir, "bar.capnp")) 55 | 56 | m = capnp._MallocMessageBuilder() 57 | foo = m.init_root(foo.Foo) 58 | m2 = capnp._MallocMessageBuilder() 59 | bar = m2.init_root(bar.Bar) 60 | 61 | foo.name = "foo" 62 | 63 | with pytest.raises(Exception): 64 | bar.foo = foo 65 | 66 | 67 | def test_defualt_import_hook(): 68 | # Make sure any previous imports of addressbook_capnp are gone 69 | capnp.cleanup_global_schema_parser() 70 | 71 | import addressbook_capnp # noqa: F401 72 | 73 | 74 | def test_dash_import(): 75 | import addressbook_with_dashes_capnp # noqa: F401 76 | 77 | 78 | def test_spaces_import(): 79 | import addressbook_with_spaces_capnp # noqa: F401 80 | 81 | 82 | def test_add_import_hook(): 83 | capnp.add_import_hook() 84 | 85 | # Make sure any previous imports of addressbook_capnp are gone 86 | capnp.cleanup_global_schema_parser() 87 | 88 | import addressbook_capnp 89 | 90 | addressbook_capnp.AddressBook.new_message() 91 | 92 | 93 | def test_multiple_add_import_hook(): 94 | capnp.add_import_hook() 95 | capnp.add_import_hook() 96 | 97 | # Make sure any previous imports of addressbook_capnp are gone 98 | capnp.cleanup_global_schema_parser() 99 | 100 | import addressbook_capnp 101 | 102 | addressbook_capnp.AddressBook.new_message() 103 | 104 | 105 | def test_remove_import_hook(): 106 | capnp.add_import_hook() 107 | capnp.remove_import_hook() 108 | 109 | if "addressbook_capnp" in sys.modules: 110 | # hack to deal with it being imported already 111 | del sys.modules["addressbook_capnp"] 112 | 113 | with pytest.raises(ImportError): 114 | import addressbook_capnp # noqa: F401 115 | 116 | 117 | def test_bundled_import_hook(): 118 | # stream.capnp should be bundled, or provided by the system capnproto 119 | capnp.add_import_hook() 120 | from capnp import stream_capnp # noqa: F401 121 | 122 | 123 | def test_nested_import(): 124 | import schemas.parent_capnp # noqa: F401 125 | import schemas.child_capnp # noqa: F401 126 | 127 | 128 | async def test_load_capnp(foo): 129 | # test dynamically loading 130 | loader = capnp.SchemaLoader() 131 | loader.load(foo.Baz.schema.get_proto()) 132 | loader.load_dynamic(foo.Qux.schema.get_proto().node) 133 | 134 | schema = loader.get(foo.Baz.schema.get_proto().node.id).as_struct() 135 | assert "text" in schema.fieldnames 136 | assert "qux" in schema.fieldnames 137 | assert schema.fields["qux"].proto.slot.type.which == "struct" 138 | 139 | class Wrapper(foo.Wrapper.Server): 140 | async def wrapped(self, object, **kwargs): 141 | assert isinstance(object, capnp.lib.capnp._DynamicObjectReader) 142 | baz_ = object.as_struct(schema) 143 | assert baz_.text == "test" 144 | assert baz_.qux.id == 2 145 | 146 | # test calling into the wrapper with a Baz message. 147 | baz_ = foo.Baz.new_message() 148 | baz_.text = "test" 149 | baz_.qux.id = 2 150 | 151 | async with capnp.kj_loop(): 152 | wrapper = foo.Wrapper._new_client(Wrapper()) 153 | remote = wrapper.wrapped(baz_) 154 | await remote 155 | -------------------------------------------------------------------------------- /test/test_memory_handling.py: -------------------------------------------------------------------------------- 1 | from types import coroutine 2 | import pytest 3 | import socket 4 | import gc 5 | 6 | import capnp 7 | import test_capability 8 | import test_capability_capnp as capability 9 | 10 | 11 | @pytest.fixture(autouse=True) 12 | async def kj_loop(): 13 | async with capnp.kj_loop(): 14 | yield 15 | 16 | 17 | @coroutine 18 | def wrap(p): 19 | return (yield from p) 20 | 21 | 22 | async def test_kj_loop_await_attach(): 23 | read, write = socket.socketpair() 24 | read = await capnp.AsyncIoStream.create_connection(sock=read) 25 | write = await capnp.AsyncIoStream.create_connection(sock=write) 26 | _ = capnp.TwoPartyServer(write, bootstrap=test_capability.Server()) 27 | client = capnp.TwoPartyClient(read).bootstrap().cast_as(capability.TestInterface) 28 | t = wrap(client.foo(5, True).__await__()) 29 | del client 30 | del read 31 | gc.collect() 32 | await t 33 | -------------------------------------------------------------------------------- /test/test_object.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import capnp 3 | import os 4 | 5 | this_dir = os.path.dirname(__file__) 6 | 7 | 8 | @pytest.fixture 9 | def addressbook(): 10 | return capnp.load(os.path.join(this_dir, "addressbook.capnp")) 11 | 12 | 13 | def test_object_basic(addressbook): 14 | obj = capnp._MallocMessageBuilder().get_root_as_any() 15 | person = obj.as_struct(addressbook.Person) 16 | person.name = "test" 17 | person.id = 1000 18 | 19 | same_person = obj.as_struct(addressbook.Person) 20 | assert same_person.name == "test" 21 | assert same_person.id == 1000 22 | 23 | obj_r = obj.as_reader() 24 | same_person = obj_r.as_struct(addressbook.Person) 25 | assert same_person.name == "test" 26 | assert same_person.id == 1000 27 | 28 | 29 | def test_object_list(addressbook): 30 | obj = capnp._MallocMessageBuilder().get_root_as_any() 31 | listSchema = capnp._ListSchema(addressbook.Person) 32 | people = obj.init_as_list(listSchema, 2) 33 | person = people[0] 34 | person.name = "test" 35 | person.id = 1000 36 | person = people[1] 37 | person.name = "test2" 38 | person.id = 1001 39 | 40 | same_person = obj.as_list(listSchema) 41 | assert same_person[0].name == "test" 42 | assert same_person[0].id == 1000 43 | assert same_person[1].name == "test2" 44 | assert same_person[1].id == 1001 45 | 46 | obj_r = obj.as_reader() 47 | same_person = obj_r.as_list(listSchema) 48 | assert same_person[0].name == "test" 49 | assert same_person[0].id == 1000 50 | assert same_person[1].name == "test2" 51 | assert same_person[1].id == 1001 52 | -------------------------------------------------------------------------------- /test/test_response.capnp: -------------------------------------------------------------------------------- 1 | @0x84249be5c3bff005; 2 | 3 | interface Foo { 4 | foo @0 () -> (val :UInt32); 5 | } 6 | 7 | struct Bar { 8 | foo @0 :Foo; 9 | } 10 | 11 | interface Baz { 12 | grault @0 () -> (bar: Bar); 13 | } 14 | -------------------------------------------------------------------------------- /test/test_response.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import capnp 4 | import test_response_capnp 5 | 6 | 7 | @pytest.fixture(autouse=True) 8 | async def kj_loop(): 9 | async with capnp.kj_loop(): 10 | yield 11 | 12 | 13 | class FooServer(test_response_capnp.Foo.Server): 14 | def __init__(self, val=1): 15 | self.val = val 16 | 17 | async def foo(self, **kwargs): 18 | return 1 19 | 20 | 21 | class BazServer(test_response_capnp.Baz.Server): 22 | def __init__(self, val=1): 23 | self.val = val 24 | 25 | async def grault(self, **kwargs): 26 | return {"foo": FooServer()} 27 | 28 | 29 | async def test_response_reference(): 30 | baz = test_response_capnp.Baz._new_client(BazServer()) 31 | 32 | bar = (await baz.grault()).bar 33 | 34 | foo = bar.foo 35 | # This used to cause an exception about invalid pointers because the response got garbage collected 36 | assert (await foo.foo()).val == 1 37 | 38 | 39 | async def test_response_reference2(): 40 | baz = test_response_capnp.Baz._new_client(BazServer()) 41 | 42 | bar = (await baz.grault()).bar 43 | 44 | # This always worked since it saved the intermediate response object 45 | response = await baz.grault() 46 | bar = response.bar 47 | foo = bar.foo 48 | assert (await foo.foo()).val == 1 49 | -------------------------------------------------------------------------------- /test/test_rpc.py: -------------------------------------------------------------------------------- 1 | """ 2 | rpc test 3 | """ 4 | 5 | import pytest 6 | import capnp 7 | import socket 8 | 9 | import test_capability_capnp 10 | 11 | 12 | @pytest.fixture(autouse=True) 13 | async def kj_loop(): 14 | async with capnp.kj_loop(): 15 | yield 16 | 17 | 18 | class Server(test_capability_capnp.TestInterface.Server): 19 | def __init__(self, val=100): 20 | self.val = val 21 | 22 | async def foo(self, i, j, **kwargs): 23 | return str(i * 5 + self.val) 24 | 25 | 26 | async def test_simple_rpc_with_options(): 27 | read, write = socket.socketpair() 28 | read = await capnp.AsyncIoStream.create_connection(sock=read) 29 | write = await capnp.AsyncIoStream.create_connection(sock=write) 30 | 31 | _ = capnp.TwoPartyServer(write, bootstrap=Server()) 32 | # This traversal limit is too low to receive the response in, so we expect 33 | # an exception during the call. 34 | client = capnp.TwoPartyClient(read, traversal_limit_in_words=1) 35 | 36 | with pytest.raises(capnp.KjException): 37 | cap = client.bootstrap().cast_as(test_capability_capnp.TestInterface) 38 | 39 | remote = cap.foo(i=5) 40 | _ = remote.wait() 41 | 42 | 43 | async def test_simple_rpc_bootstrap(): 44 | read, write = socket.socketpair() 45 | read = await capnp.AsyncIoStream.create_connection(sock=read) 46 | write = await capnp.AsyncIoStream.create_connection(sock=write) 47 | 48 | _ = capnp.TwoPartyServer(write, bootstrap=Server(100)) 49 | client = capnp.TwoPartyClient(read) 50 | 51 | cap = client.bootstrap() 52 | cap = cap.cast_as(test_capability_capnp.TestInterface) 53 | 54 | # Check not only that the methods are there, but also that they are listed 55 | # as expected. 56 | assert "foo" in dir(cap) 57 | assert "bar" in dir(cap) 58 | assert "buz" in dir(cap) 59 | assert "bam" in dir(cap) 60 | 61 | remote = cap.foo(i=5) 62 | response = await remote 63 | 64 | assert response.x == "125" 65 | -------------------------------------------------------------------------------- /test/test_rpc_calculator.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import os 3 | import socket 4 | import sys # add examples dir to sys.path 5 | import pytest 6 | 7 | import capnp 8 | 9 | examples_dir = os.path.join(os.path.dirname(__file__), "..", "examples") 10 | sys.path.append(examples_dir) 11 | 12 | import async_calculator_client # noqa: E402 13 | import async_calculator_server # noqa: E402 14 | 15 | 16 | @pytest.fixture(autouse=True) 17 | async def kj_loop(): 18 | async with capnp.kj_loop(): 19 | yield 20 | 21 | 22 | async def test_calculator(): 23 | read, write = socket.socketpair() 24 | read = await capnp.AsyncIoStream.create_connection(sock=read) 25 | write = await capnp.AsyncIoStream.create_connection(sock=write) 26 | 27 | _ = capnp.TwoPartyServer(write, bootstrap=async_calculator_server.CalculatorImpl()) 28 | await async_calculator_client.main(read) 29 | 30 | 31 | async def test_calculator_gc(): 32 | def new_evaluate_impl(old_evaluate_impl): 33 | def call(*args, **kwargs): 34 | gc.collect() 35 | return old_evaluate_impl(*args, **kwargs) 36 | 37 | return call 38 | 39 | read, write = socket.socketpair() 40 | read = await capnp.AsyncIoStream.create_connection(sock=read) 41 | write = await capnp.AsyncIoStream.create_connection(sock=write) 42 | 43 | # inject a gc.collect to the beginning of every evaluate_impl call 44 | evaluate_impl_orig = async_calculator_server.evaluate_impl 45 | async_calculator_server.evaluate_impl = new_evaluate_impl(evaluate_impl_orig) 46 | 47 | _ = capnp.TwoPartyServer(write, bootstrap=async_calculator_server.CalculatorImpl()) 48 | await async_calculator_client.main(read) 49 | 50 | async_calculator_server.evaluate_impl = evaluate_impl_orig 51 | -------------------------------------------------------------------------------- /test/test_schema.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import capnp 3 | import os 4 | 5 | this_dir = os.path.dirname(__file__) 6 | 7 | 8 | @pytest.fixture 9 | def addressbook(): 10 | return capnp.load(os.path.join(this_dir, "addressbook.capnp")) 11 | 12 | 13 | @pytest.fixture 14 | def annotations(): 15 | return capnp.load(os.path.join(this_dir, "annotations.capnp")) 16 | 17 | 18 | def test_basic_schema(addressbook): 19 | assert addressbook.Person.schema.fieldnames[0] == "id" 20 | 21 | 22 | def test_list_schema(addressbook): 23 | peopleField = addressbook.AddressBook.schema.fields["people"] 24 | personType = peopleField.schema.elementType 25 | 26 | assert personType.node.id == addressbook.Person.schema.node.id 27 | 28 | personListSchema = capnp._ListSchema(addressbook.Person) 29 | 30 | assert personListSchema.elementType.node.id == addressbook.Person.schema.node.id 31 | 32 | 33 | def test_annotations(annotations): 34 | assert annotations.schema.node.annotations[0].value.text == "TestFile" 35 | 36 | annotation = annotations.TestAnnotationOne.schema.node.annotations[0] 37 | assert annotation.value.text == "Test" 38 | 39 | annotation = annotations.TestAnnotationTwo.schema.node.annotations[0] 40 | assert annotation.value.struct.as_struct(annotations.AnnotationStruct).test == 100 41 | 42 | annotation = annotations.TestAnnotationThree.schema.node.annotations[0] 43 | annotation_list = annotation.value.list.as_list( 44 | capnp._ListSchema(annotations.AnnotationStruct) 45 | ) 46 | assert annotation_list[0].test == 100 47 | assert annotation_list[1].test == 101 48 | 49 | annotation = annotations.TestAnnotationFour.schema.node.annotations[0] 50 | annotation_list = annotation.value.list.as_list( 51 | capnp._ListSchema(capnp.types.UInt16) 52 | ) 53 | assert annotation_list[0] == 200 54 | assert annotation_list[1] == 201 55 | -------------------------------------------------------------------------------- /test/test_serialization.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from contextlib import contextmanager 3 | 4 | import pytest 5 | import capnp 6 | import os 7 | import platform 8 | import test_regression 9 | import tempfile 10 | import pickle 11 | import mmap 12 | import sys 13 | 14 | this_dir = os.path.dirname(__file__) 15 | 16 | 17 | @pytest.fixture 18 | def all_types(): 19 | return capnp.load(os.path.join(this_dir, "all_types.capnp")) 20 | 21 | 22 | def test_roundtrip_file(all_types): 23 | f = tempfile.TemporaryFile() 24 | msg = all_types.TestAllTypes.new_message() 25 | test_regression.init_all_types(msg) 26 | msg.write(f) 27 | 28 | f.seek(0) 29 | msg = all_types.TestAllTypes.read(f) 30 | test_regression.check_all_types(msg) 31 | 32 | 33 | def test_roundtrip_file_packed(all_types): 34 | f = tempfile.TemporaryFile() 35 | msg = all_types.TestAllTypes.new_message() 36 | test_regression.init_all_types(msg) 37 | msg.write_packed(f) 38 | 39 | f.seek(0) 40 | msg = all_types.TestAllTypes.read_packed(f) 41 | test_regression.check_all_types(msg) 42 | 43 | 44 | def test_roundtrip_bytes(all_types): 45 | msg = all_types.TestAllTypes.new_message() 46 | test_regression.init_all_types(msg) 47 | message_bytes = msg.to_bytes() 48 | 49 | with all_types.TestAllTypes.from_bytes(message_bytes) as msg: 50 | test_regression.check_all_types(msg) 51 | 52 | 53 | @pytest.mark.skipif( 54 | platform.python_implementation() == "PyPy", 55 | reason="TODO: Investigate why this works on CPython but fails on PyPy.", 56 | ) 57 | def test_roundtrip_segments(all_types): 58 | msg = all_types.TestAllTypes.new_message() 59 | test_regression.init_all_types(msg) 60 | segments = msg.to_segments() 61 | msg = all_types.TestAllTypes.from_segments(segments) 62 | test_regression.check_all_types(msg) 63 | 64 | 65 | @pytest.mark.skipif( 66 | sys.version_info[0] < 3, 67 | reason="mmap doesn't implement the buffer interface under python 2.", 68 | ) 69 | def test_roundtrip_bytes_mmap(all_types): 70 | msg = all_types.TestAllTypes.new_message() 71 | test_regression.init_all_types(msg) 72 | 73 | with tempfile.TemporaryFile() as f: 74 | msg.write(f) 75 | length = f.tell() 76 | 77 | f.seek(0) 78 | memory = mmap.mmap(f.fileno(), length) 79 | 80 | with all_types.TestAllTypes.from_bytes(memory) as msg: 81 | test_regression.check_all_types(msg) 82 | 83 | 84 | def test_roundtrip_bytes_buffer(all_types): 85 | msg = all_types.TestAllTypes.new_message() 86 | test_regression.init_all_types(msg) 87 | 88 | b = msg.to_bytes() 89 | v = memoryview(b) 90 | try: 91 | with all_types.TestAllTypes.from_bytes(v) as msg: 92 | test_regression.check_all_types(msg) 93 | finally: 94 | v.release() 95 | 96 | 97 | def test_roundtrip_bytes_fail(all_types): 98 | with pytest.raises(TypeError): 99 | with all_types.TestAllTypes.from_bytes(42) as _: 100 | pass 101 | 102 | 103 | @pytest.mark.skipif( 104 | platform.python_implementation() == "PyPy", 105 | reason="This works in PyPy 4.0.1 but travisci's version of PyPy has some bug that fails this test.", 106 | ) 107 | def test_roundtrip_bytes_packed(all_types): 108 | msg = all_types.TestAllTypes.new_message() 109 | test_regression.init_all_types(msg) 110 | message_bytes = msg.to_bytes_packed() 111 | 112 | msg = all_types.TestAllTypes.from_bytes_packed(message_bytes) 113 | test_regression.check_all_types(msg) 114 | 115 | 116 | @contextmanager 117 | def _warnings( 118 | expected_count=2, expected_text="This message has already been written once." 119 | ): 120 | with warnings.catch_warnings(record=True) as w: 121 | yield 122 | 123 | assert len(w) == expected_count 124 | assert all(issubclass(x.category, UserWarning) for x in w), w 125 | assert all(expected_text in str(x.message) for x in w), w 126 | 127 | 128 | def test_roundtrip_file_multiple(all_types): 129 | f = tempfile.TemporaryFile() 130 | msg = all_types.TestAllTypes.new_message() 131 | test_regression.init_all_types(msg) 132 | msg.write(f) 133 | with _warnings(2): 134 | msg.write(f) 135 | msg.write(f) 136 | 137 | f.seek(0) 138 | i = 0 139 | for msg in all_types.TestAllTypes.read_multiple(f): 140 | test_regression.check_all_types(msg) 141 | i += 1 142 | assert i == 3 143 | 144 | 145 | def test_roundtrip_bytes_multiple(all_types): 146 | msg = all_types.TestAllTypes.new_message() 147 | test_regression.init_all_types(msg) 148 | 149 | msgs = msg.to_bytes() 150 | with _warnings(2): 151 | msgs += msg.to_bytes() 152 | msgs += msg.to_bytes() 153 | 154 | i = 0 155 | for msg in all_types.TestAllTypes.read_multiple_bytes(msgs): 156 | test_regression.check_all_types(msg) 157 | i += 1 158 | assert i == 3 159 | 160 | 161 | def test_roundtrip_file_multiple_packed(all_types): 162 | f = tempfile.TemporaryFile() 163 | msg = all_types.TestAllTypes.new_message() 164 | test_regression.init_all_types(msg) 165 | msg.write_packed(f) 166 | with _warnings(2): 167 | msg.write_packed(f) 168 | msg.write_packed(f) 169 | 170 | f.seek(0) 171 | i = 0 172 | for msg in all_types.TestAllTypes.read_multiple_packed(f): 173 | test_regression.check_all_types(msg) 174 | i += 1 175 | assert i == 3 176 | 177 | 178 | def test_roundtrip_bytes_multiple_packed(all_types): 179 | msg = all_types.TestAllTypes.new_message() 180 | test_regression.init_all_types(msg) 181 | 182 | msgs = msg.to_bytes_packed() 183 | with _warnings(2): 184 | msgs += msg.to_bytes_packed() 185 | msgs += msg.to_bytes_packed() 186 | 187 | i = 0 188 | for msg in all_types.TestAllTypes.read_multiple_bytes_packed(msgs): 189 | test_regression.check_all_types(msg) 190 | i += 1 191 | assert i == 3 192 | 193 | 194 | def test_file_and_bytes(all_types): 195 | f = tempfile.TemporaryFile() 196 | msg = all_types.TestAllTypes.new_message() 197 | test_regression.init_all_types(msg) 198 | msg.write(f) 199 | 200 | f.seek(0) 201 | 202 | with _warnings(1): 203 | assert f.read() == msg.to_bytes() 204 | 205 | 206 | def test_file_and_bytes_packed(all_types): 207 | f = tempfile.TemporaryFile() 208 | msg = all_types.TestAllTypes.new_message() 209 | test_regression.init_all_types(msg) 210 | msg.write_packed(f) 211 | 212 | f.seek(0) 213 | 214 | with _warnings(1): 215 | assert f.read() == msg.to_bytes_packed() 216 | 217 | 218 | def test_pickle(all_types): 219 | msg = all_types.TestAllTypes.new_message() 220 | test_regression.init_all_types(msg) 221 | data = pickle.dumps(msg) 222 | msg2 = pickle.loads(data) 223 | 224 | test_regression.check_all_types(msg2) 225 | 226 | 227 | def test_from_bytes_traversal_limit(all_types): 228 | size = 1024 229 | bld = all_types.TestAllTypes.new_message() 230 | bld.init("structList", size) 231 | data = bld.to_bytes() 232 | 233 | with all_types.TestAllTypes.from_bytes(data) as msg: 234 | with pytest.raises(capnp.KjException): 235 | for i in range(0, size): 236 | msg.structList[i].uInt8Field == 0 237 | 238 | with all_types.TestAllTypes.from_bytes(data, traversal_limit_in_words=2**62) as msg: 239 | for i in range(0, size): 240 | assert msg.structList[i].uInt8Field == 0 241 | 242 | 243 | def test_from_bytes_packed_traversal_limit(all_types): 244 | size = 1024 245 | bld = all_types.TestAllTypes.new_message() 246 | bld.init("structList", size) 247 | data = bld.to_bytes_packed() 248 | 249 | msg = all_types.TestAllTypes.from_bytes_packed(data) 250 | with pytest.raises(capnp.KjException): 251 | for i in range(0, size): 252 | msg.structList[i].uInt8Field == 0 253 | 254 | msg = all_types.TestAllTypes.from_bytes_packed(data, traversal_limit_in_words=2**62) 255 | for i in range(0, size): 256 | assert msg.structList[i].uInt8Field == 0 257 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38,py39,py310,py311,py12 3 | skipsdist = True 4 | 5 | [testenv] 6 | deps= 7 | pkgconfig 8 | Jinja2 9 | pytest 10 | pytest-asyncio 11 | cython>=3 12 | 13 | commands = 14 | pip install . 15 | py.test {posargs} 16 | 17 | setenv = 18 | CFLAGS='-stdlib=libc++' 19 | CXXFLAGS='-stdlib=libc++' 20 | --------------------------------------------------------------------------------