├── 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 |
--------------------------------------------------------------------------------