├── MANIFEST.in ├── .gitignore ├── nnpy ├── errors.py ├── __init__.py ├── tests.py └── socket.py ├── LICENSE ├── setup.py ├── README.rst └── generate.py /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include generate.py 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | _nnpy.* 3 | build/ 4 | dist/ 5 | nnpy.egg-info/ 6 | nnpy/constants.py 7 | .coverage 8 | 9 | -------------------------------------------------------------------------------- /nnpy/errors.py: -------------------------------------------------------------------------------- 1 | from _nnpy import ffi, lib as nanomsg 2 | 3 | class NNError(Exception): 4 | def __init__(self, error_no, *args, **kwargs): 5 | super(NNError, self).__init__(*args, **kwargs) 6 | self.error_no = error_no 7 | 8 | def convert(rc, value=None): 9 | if rc < 0: 10 | error_no = nanomsg.nn_errno() 11 | chars = nanomsg.nn_strerror(error_no) 12 | msg = ffi.string(chars).decode() 13 | raise NNError(error_no, msg) 14 | if callable(value): 15 | return value() 16 | return value 17 | 18 | -------------------------------------------------------------------------------- /nnpy/__init__.py: -------------------------------------------------------------------------------- 1 | from .constants import * 2 | from _nnpy import ffi, lib as nanomsg 3 | from . import errors 4 | import os 5 | 6 | from .errors import NNError 7 | from .socket import Socket 8 | 9 | class PollSet(object): 10 | 11 | def __init__(self, *args): 12 | self.data = [(sock.sock, events, 0) for (sock, events) in args] 13 | self.fd_set = ffi.new('struct nn_pollfd[]', self.data) 14 | 15 | def poll(self, timeout=0): 16 | rc = nanomsg.nn_poll(self.fd_set, len(self.data), timeout) 17 | return errors.convert(rc, lambda: [fd.revents for fd in self.fd_set]) 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), 3 | to deal in the Software without restriction, including without limitation 4 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 5 | and/or sell copies of the Software, and to permit persons to whom 6 | the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included 9 | in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 14 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 16 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 17 | IN THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='nnpy', 5 | version='1.4.2', 6 | url='https://github.com/nanomsg/nnpy', 7 | license='MIT', 8 | author='Dirkjan Ochtman', 9 | author_email='dirkjan@ochtman.nl', 10 | description='cffi-based Python bindings for nanomsg', 11 | long_description=open('README.rst').read(), 12 | classifiers=[ 13 | 'Development Status :: 5 - Production/Stable', 14 | 'Environment :: Web Environment', 15 | 'Intended Audience :: Developers', 16 | 'License :: OSI Approved :: MIT License', 17 | 'Operating System :: OS Independent', 18 | 'Programming Language :: Python', 19 | 'Programming Language :: Python :: 2.7', 20 | 'Programming Language :: Python :: 3', 21 | 'Programming Language :: Python :: 3.3', 22 | 'Programming Language :: Python :: 3.4', 23 | 'Programming Language :: Python :: Implementation :: CPython', 24 | ], 25 | packages=['nnpy'], 26 | setup_requires=["cffi>=1.0.0"], 27 | cffi_modules=["generate.py:ffi"], 28 | install_requires=['cffi'], 29 | ) 30 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | nnpy: cffi-based Python bindings for nanomsg 2 | ============================================ 3 | 4 | This library is no longer being maintained. 5 | ------------------------------------------- 6 | 7 | Instead, consider using `pynng`_. 8 | 9 | Is what it says on the tin. Stay tuned for more. Dependencies: 10 | 11 | - Python 2.7 or 3.4 12 | - cffi (http://cffi.readthedocs.org/en/latest/) and its dependencies 13 | - nanomsg (tested with 1.0) 14 | 15 | How to install 16 | -------------- 17 | 18 | The usual should work: 19 | 20 | .. code-block:: shell 21 | 22 | $ sudo pip install nnpy 23 | 24 | Getting started 25 | --------------- 26 | 27 | .. code-block:: python 28 | 29 | import nnpy 30 | 31 | pub = nnpy.Socket(nnpy.AF_SP, nnpy.PUB) 32 | pub.bind('inproc://foo') 33 | 34 | sub = nnpy.Socket(nnpy.AF_SP, nnpy.SUB) 35 | sub.connect('inproc://foo') 36 | sub.setsockopt(nnpy.SUB, nnpy.SUB_SUBSCRIBE, '') 37 | 38 | pub.send('hello, world') 39 | print(sub.recv()) 40 | 41 | Related Projects 42 | ---------------- 43 | 44 | - `nanomsg `_ 45 | - `aionn `_ - asyncio messaging library based on nnpy 46 | 47 | .. _pynng: https://github.com/codypiersall/pynng 48 | -------------------------------------------------------------------------------- /nnpy/tests.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import nnpy, unittest 3 | 4 | class Tests(unittest.TestCase): 5 | def test_basic(self): 6 | 7 | pub = nnpy.Socket(nnpy.AF_SP, nnpy.PUB) 8 | pub.setsockopt(nnpy.SOL_SOCKET, nnpy.IPV4ONLY, 0) 9 | pub.bind('inproc://foo') 10 | self.assertEqual(pub.getsockopt(nnpy.SOL_SOCKET, nnpy.DOMAIN), 1) 11 | 12 | sub = nnpy.Socket(nnpy.AF_SP, nnpy.SUB) 13 | sub_conn = sub.connect('inproc://foo') 14 | sub.setsockopt(nnpy.SUB, nnpy.SUB_SUBSCRIBE, '') 15 | 16 | pub.send('FLUB') 17 | poller = nnpy.PollSet((sub, nnpy.POLLIN)) 18 | self.assertEqual(len(poller.poll()), 1) 19 | self.assertEqual(poller.poll()[0], 1) 20 | self.assertEqual(sub.recv().decode(), 'FLUB') 21 | self.assertEqual(pub.get_statistic(nnpy.STAT_MESSAGES_SENT), 1) 22 | pub.close() 23 | sub.shutdown(sub_conn) 24 | 25 | def test_basic_nn_error(self): 26 | address = 'inproc://timeout-always' 27 | 28 | req = nnpy.Socket(nnpy.AF_SP, nnpy.REQ) 29 | req.connect(address) 30 | 31 | req.setsockopt(nnpy.SOL_SOCKET, nnpy.RCVTIMEO, 500) 32 | 33 | with self.assertRaises(nnpy.errors.NNError): 34 | req.recv() 35 | 36 | def suite(): 37 | return unittest.makeSuite(Tests) 38 | 39 | if __name__ == '__main__': 40 | unittest.main(defaultTest='suite') 41 | -------------------------------------------------------------------------------- /nnpy/socket.py: -------------------------------------------------------------------------------- 1 | from . import errors, ffi, nanomsg 2 | import sys 3 | 4 | NN_MSG = int(ffi.cast("size_t", -1)) 5 | 6 | ustr = str if sys.version_info[0] > 2 else unicode 7 | 8 | class Socket(object): 9 | """ 10 | Nanomsg scalability protocols (SP) socket. 11 | 12 | .. seealso:: `nanomsg `_ 13 | """ 14 | def __init__(self, domain, protocol): 15 | """ 16 | Create SP socket. 17 | 18 | :param domain: Socket domain `AF_SP` or `AF_SP_RAW`. 19 | :param protocol: Type of the socket determining its exact 20 | semantics. 21 | 22 | .. seealso:: `nn_socket `_ 23 | """ 24 | self.sock = nanomsg.nn_socket(domain, protocol) 25 | 26 | def close(self): 27 | rc = nanomsg.nn_close(self.sock) 28 | return errors.convert(rc, rc) 29 | 30 | def shutdown(self, who): 31 | rc = nanomsg.nn_shutdown(self.sock, who) 32 | return errors.convert(rc, rc) 33 | 34 | def getsockopt(self, level, option): 35 | buf = ffi.new('int*') 36 | size = ffi.new('size_t*') 37 | size[0] = 4 38 | rc = nanomsg.nn_getsockopt(self.sock, level, option, buf, size) 39 | return errors.convert(rc, lambda: buf[0]) 40 | 41 | def setsockopt(self, level, option, value): 42 | if isinstance(value, int): 43 | buf = ffi.new('int*') 44 | buf[0] = value 45 | vlen = 4 46 | elif isinstance(value, ustr): 47 | buf = ffi.new('char[%s]' % len(value), value.encode()) 48 | vlen = len(value) 49 | elif isinstance(value, bytes): 50 | buf = ffi.new('char[%s]' % len(value), value) 51 | vlen = len(value) 52 | else: 53 | raise TypeError("value must be a int, str or bytes") 54 | rc = nanomsg.nn_setsockopt(self.sock, level, option, buf, vlen) 55 | errors.convert(rc) 56 | 57 | def bind(self, addr): 58 | addr = addr.encode() if isinstance(addr, ustr) else addr 59 | buf = ffi.new('char[]', addr) 60 | rc = nanomsg.nn_bind(self.sock, buf) 61 | return errors.convert(rc, rc) 62 | 63 | def connect(self, addr): 64 | addr = addr.encode() if isinstance(addr, ustr) else addr 65 | buf = ffi.new('char[]', addr) 66 | rc = nanomsg.nn_connect(self.sock, buf) 67 | return errors.convert(rc, rc) 68 | 69 | def send(self, data, flags=0): 70 | # Some data types can use a zero-copy buffer creation strategy when 71 | # paired with new versions of CFFI. Namely, CFFI 1.8 supports `bytes` 72 | # types with `from_buffer`, which is about 18% faster. We try the fast 73 | # way first and degrade as needed for the platform. 74 | try: 75 | buf = ffi.from_buffer(data) 76 | except TypeError: 77 | data = data.encode() if isinstance(data, ustr) else data 78 | buf = ffi.new('char[%i]' % len(data), data) 79 | rc = nanomsg.nn_send(self.sock, buf, len(data), flags) 80 | return errors.convert(rc, rc) 81 | 82 | def recv(self, flags=0): 83 | buf = ffi.new('char**') 84 | rc = nanomsg.nn_recv(self.sock, buf, NN_MSG, flags) 85 | errors.convert(rc) 86 | s = ffi.buffer(buf[0], rc)[:] 87 | nanomsg.nn_freemsg(buf[0]) 88 | return s 89 | 90 | def get_statistic(self, statistic): 91 | rc = nanomsg.nn_get_statistic(self.sock, statistic) 92 | return errors.convert(rc, rc) 93 | -------------------------------------------------------------------------------- /generate.py: -------------------------------------------------------------------------------- 1 | from cffi import FFI 2 | import os 3 | 4 | try: 5 | import ConfigParser as cfgparser 6 | except ImportError: 7 | # try py3 import 8 | import configparser as cfgparser 9 | 10 | SITE_CFG = 'site.cfg' 11 | 12 | DEFAULT_INCLUDE_DIRS = ['/usr/include/nanomsg', '/usr/local/include/nanomsg'] 13 | DEFAULT_HOST_LIBRARY = 'nanomsg' 14 | 15 | BLOCKS = {'{': '}', '(': ')'} 16 | DEFINITIONS = ''' 17 | struct nn_pollfd { 18 | int fd; 19 | short events; 20 | short revents; 21 | }; 22 | ''' 23 | 24 | def header_files(include_paths): 25 | for dir in include_paths: 26 | if os.path.exists(dir): 27 | break 28 | return {fn: os.path.join(dir, fn) for fn in os.listdir(dir) 29 | if fn.endswith('.h')} 30 | 31 | def functions(hfiles): 32 | 33 | lines = [] 34 | for fn, path in hfiles.items(): 35 | with open(path) as f: 36 | cont = '' 37 | for ln in f: 38 | 39 | if cont == ',': 40 | lines.append(ln) 41 | cont = '' 42 | if cont in BLOCKS: 43 | lines.append(ln) 44 | if BLOCKS[cont] in ln: 45 | cont = '' 46 | if not (ln.startswith('NN_EXPORT') 47 | or ln.startswith('typedef')): 48 | continue 49 | 50 | lines.append(ln) 51 | cont = ln.strip()[-1] 52 | 53 | return ''.join(ln[10:] if ln.startswith('NN_') else ln for ln in lines) 54 | 55 | def symbols(ffi, host_library): 56 | 57 | nanomsg = ffi.dlopen(host_library) 58 | lines = [] 59 | for i in range(1024): 60 | 61 | val = ffi.new('int*') 62 | name = nanomsg.nn_symbol(i, val) 63 | if name == ffi.NULL: 64 | break 65 | 66 | name = ffi.string(name).decode() 67 | name = name[3:] if name.startswith('NN_') else name 68 | lines.append('%s = %s' % (name, val[0])) 69 | 70 | return '\n'.join(lines) + '\n' 71 | 72 | def create_module(): 73 | 74 | # Set defaults 75 | 76 | set_source_args = { 77 | 'include_dirs': DEFAULT_INCLUDE_DIRS 78 | } 79 | host_library = DEFAULT_HOST_LIBRARY 80 | 81 | # Read overrides for cross-compilation support from site.cfg 82 | 83 | if os.path.isfile(SITE_CFG): 84 | parser = cfgparser.ConfigParser() 85 | 86 | if parser.read(SITE_CFG): 87 | 88 | parsed_cfg = parser.defaults() 89 | for param in ['include_dirs', 'library_dirs']: 90 | if param in parsed_cfg: 91 | set_source_args[param] = parsed_cfg[param].split(',') 92 | 93 | if 'host_library' in parsed_cfg: 94 | host_library = parsed_cfg['host_library'] 95 | 96 | # Add some more directories from the environment 97 | 98 | if 'CPATH' in os.environ: 99 | cpaths = os.getenv('CPATH').split(os.pathsep) 100 | set_source_args['include_dirs'] += [os.path.join(p, 'nanomsg') 101 | for p in cpaths] 102 | 103 | hfiles = header_files(set_source_args['include_dirs']) 104 | # remove ws.h due to https://github.com/nanomsg/nanomsg/issues/467 105 | hfiles.pop('ws.h', None) 106 | 107 | # Build FFI module and write out the constants 108 | 109 | ffi = FFI() 110 | ffi.cdef(DEFINITIONS) 111 | ffi.cdef(functions(hfiles)) 112 | ffi.set_source('_nnpy', '\n'.join('#include <%s>' % fn for fn in hfiles), 113 | libraries=['nanomsg'], **set_source_args) 114 | 115 | with open('nnpy/constants.py', 'w') as f: 116 | f.write(symbols(ffi, host_library)) 117 | 118 | return ffi 119 | 120 | ffi = create_module() 121 | 122 | if __name__ == '__main__': 123 | ffi.compile() 124 | --------------------------------------------------------------------------------