* Integrate the KJ event loop into Python's asyncio event loop Fix #256 This PR attempts to remove the slow and expensive polling behavior for asyncio in favor of proper linking of the KJ event loop to the asyncio event loop. * Don't memcopy buffer * Improve promise cancellation and prepare for timer implementation * Add attribution for asyncProvider.cpp * Implement timeout * Cleanup * First round of simplifications * Add more a_wait functions and a shutdown function * Fix edge-cases with loop shutdown * Clean up calculator examples * Cleanup * Cleanup * Reformat * Fix warnings * Reformat again * Compatibility with macos * Inline the asyncio loop in some places where this is feasible * Add todo * Fix * Remove synchronous wait * Wrap fd listening callbacks in a class * Remove poll_forever * Remove the thread-local/thread-global optimization This will not matter much soon anyway, and simplifies things * Share promise code by using fused types * Improve refcounting of python objects in promises We replace many instances of PyObject* by Own<PyRefCounter> for more automatic reference management. * Code wrapPyFunc in a similar way to wrapPyFuncNoArg * Refactor capabilityHelper, fix several memory bugs for promises and add __await__ * Improve promise ownership, reduce memory leaks Promise wrappers now hold a Own<Promise<Own<PyRefCounter>>> object. This might seem like excessive nesting of objects (which to some degree it is, but with good reason): - The outer Own is needed because Cython cannot allocate objects without a nullary constructor on the stack (Promise doesn't have a nullary constructor). Additionally, I believe it would be difficult or impossible to detect when a promise is cancelled/moved if we use a bare Promise. - Every promise returns a Owned PyRefCounter. PyRefCounter makes sure that a reference to the returned object keeps existing until the promise is fulfilled or cancelled. Previously, this was attempted using attach, which is redundant and makes reasoning about PyINCREF and PyDECREF very difficult. - Because a promise holds a Own<Promise<...>>, when we perform any kind of action on that promise (a_wait, then, ...), we have to explicitly move() the ownership around. This will leave the original promise with a NULL-pointer, which we can easily detect as a cancelled promise. Promises now only hold references to their 'parents' when strictly needed. This should reduce memory pressure. * Simplify and test the promise joining functionality * Attach forgotten parent * Catch exceptions in add_reader and friends * Further cleanup of memory leaks * Get rid of a_wait() in examples * Cancel all fd read operations when the python asyncio loop is closed * Formatting * Remove support for capnp < 7000 * Bring asyncProvider.cpp more in line with upstream async-io-unix.c++ It was originally copied from the nodejs implementation, which in turn copied from async-io-unix.c++. But that copy is pretty old. * Fix a bug that caused file descriptors to never be closed * Implement AsyncIoStream based on Python transports and protocols * Get rid of asyncProvider All asyncio now goes through _AsyncIoStream * Formatting * Add __dict__ to PyAsyncIoStreamProtocol for python 3.7 * Reintroduce strange ipv4/ipv6 selection code to make ci happy * Extra pause_reading() * Work around more python bugs * Be careful to only close transport when this is still possible * Move pause_reading() workaround
pycapnp
Cap'n'proto Mailing List Documentation
Requirements
- C++14 supported compiler
- gcc 6.1+ (5+ may work)
- clang 6 (3.4+ may work)
- Visual Studio 2017+
- cmake (needed for bundled capnproto)
- ninja (macOS + Linux)
- Visual Studio 2017+
- capnproto-0.10 (>=0.7.0 will also work if linking to system libraries)
- Not necessary if using bundled capnproto
- Python development headers (i.e. Python.h)
- Distributables from python.org include these, however they are usually in a separate package on Linux distributions
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.
pycapnp has additional development dependencies, including cython and pytest. See requirements.txt for them all.
Building and installation
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.
Or you can clone the repo like so:
git clone https://github.com/capnproto/pycapnp.git
cd pycapnp
pip install .
If you wish to install using the latest upstream C++ Cap'n Proto:
pip install \
--install-option "--libcapnp-url" \
--install-option "https://github.com/capnproto/capnproto/archive/master.tar.gz" \
--install-option "--force-bundled-libcapnp" .
To force bundled python:
pip install --install-option "--force-bundled-libcapnp" .
Slightly more prompt error messages using distutils rather than pip.
python setup.py install --force-bundled-libcapnp
The bundling system isn't that smart so it might be necessary to clean up the bundled build when changing versions:
python setup.py clean
Stub-file generation
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 for more details.
Python Versions
Python 3.7+ is supported.
Development
Git flow has been abandoned, use master.
To test, use a pipenv (or install requirements.txt and run pytest manually).
pip install pipenv
pipenv install
pipenv run pytest
Binary Packages
Building a dumb binary distribution:
python setup.py bdist_dumb
Building a Python wheel distributiion:
python setup.py bdist_wheel
Documentation/Example
There is some basic documentation here.
Make sure to look at the examples. The examples are generally kept up to date with the recommended usage of the library.
The examples directory has one example that shows off pycapnp quite nicely. Here it is, reproduced:
import os
import capnp
import addressbook_capnp
def writeAddressBook(file):
addresses = addressbook_capnp.AddressBook.new_message()
people = addresses.init('people', 2)
alice = people[0]
alice.id = 123
alice.name = 'Alice'
alice.email = 'alice@example.com'
alicePhones = alice.init('phones', 1)
alicePhones[0].number = "555-1212"
alicePhones[0].type = 'mobile'
alice.employment.school = "MIT"
bob = people[1]
bob.id = 456
bob.name = 'Bob'
bob.email = 'bob@example.com'
bobPhones = bob.init('phones', 2)
bobPhones[0].number = "555-4567"
bobPhones[0].type = 'home'
bobPhones[1].number = "555-7654"
bobPhones[1].type = 'work'
bob.employment.unemployed = None
addresses.write(file)
def printAddressBook(file):
addresses = addressbook_capnp.AddressBook.read(file)
for person in addresses.people:
print(person.name, ':', person.email)
for phone in person.phones:
print(phone.type, ':', phone.number)
which = person.employment.which()
print(which)
if which == 'unemployed':
print('unemployed')
elif which == 'employer':
print('employer:', person.employment.employer)
elif which == 'school':
print('student at:', person.employment.school)
elif which == 'selfEmployed':
print('self employed')
print()
if __name__ == '__main__':
f = open('example', 'w')
writeAddressBook(f)
f = open('example', 'r')
printAddressBook(f)
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:
import capnp
import socket
import test_capability_capnp
class Server(test_capability_capnp.TestInterface.Server):
def __init__(self, val=1):
self.val = val
def foo(self, i, j, **kwargs):
return str(i * 5 + self.val)
def server(write_end):
server = capnp.TwoPartyServer(write_end, bootstrap=Server(100))
def client(read_end):
client = capnp.TwoPartyClient(read_end)
cap = client.bootstrap()
cap = cap.cast_as(test_capability_capnp.TestInterface)
remote = cap.foo(i=5)
response = remote.wait()
assert response.x == '125'
if __name__ == '__main__':
read_end, write_end = socket.socketpair(socket.AF_UNIX)
# This is a toy example using socketpair.
# In real situations, you can use any socket.
server(write_end)
client(read_end)