1b2732e9dSimarom# coding: utf-8
2b2732e9dSimarom"""Python bindings for 0MQ."""
3b2732e9dSimarom
4b2732e9dSimarom# Copyright (C) PyZMQ Developers
5b2732e9dSimarom# Distributed under the terms of the Modified BSD License.
6b2732e9dSimarom
7b2732e9dSimaromimport atexit
8b2732e9dSimarom
9b2732e9dSimaromfrom zmq.backend import Context as ContextBase
10b2732e9dSimaromfrom . import constants
11b2732e9dSimaromfrom .attrsettr import AttributeSetter
12b2732e9dSimaromfrom .constants import ENOTSUP, ctx_opt_names
13b2732e9dSimaromfrom .socket import Socket
14b2732e9dSimaromfrom zmq.error import ZMQError
15b2732e9dSimarom
16a28ff745Simarom# notice when exiting, to avoid triggering term on exit
17a28ff745Simarom_exiting = False
18a28ff745Simaromdef _notice_atexit():
19a28ff745Simarom    global _exiting
20a28ff745Simarom    _exiting = True
21a28ff745Simaromatexit.register(_notice_atexit)
22a28ff745Simarom
23a28ff745Simarom
24b2732e9dSimaromfrom zmq.utils.interop import cast_int_addr
25b2732e9dSimarom
26b2732e9dSimarom
27b2732e9dSimaromclass Context(ContextBase, AttributeSetter):
28b2732e9dSimarom    """Create a zmq Context
29b2732e9dSimarom
30b2732e9dSimarom    A zmq Context creates sockets via its ``ctx.socket`` method.
31b2732e9dSimarom    """
32b2732e9dSimarom    sockopts = None
33b2732e9dSimarom    _instance = None
34b2732e9dSimarom    _shadow = False
35b2732e9dSimarom
36b2732e9dSimarom    def __init__(self, io_threads=1, **kwargs):
37b2732e9dSimarom        super(Context, self).__init__(io_threads=io_threads, **kwargs)
38b2732e9dSimarom        if kwargs.get('shadow', False):
39b2732e9dSimarom            self._shadow = True
40b2732e9dSimarom        else:
41b2732e9dSimarom            self._shadow = False
42b2732e9dSimarom        self.sockopts = {}
43b2732e9dSimarom
44b2732e9dSimarom
45b2732e9dSimarom    def __del__(self):
46b2732e9dSimarom        """deleting a Context should terminate it, without trying non-threadsafe destroy"""
47a28ff745Simarom        if not self._shadow and not _exiting:
48b2732e9dSimarom            self.term()
49b2732e9dSimarom
50b2732e9dSimarom    def __enter__(self):
51b2732e9dSimarom        return self
52b2732e9dSimarom
53b2732e9dSimarom    def __exit__(self, *args, **kwargs):
54b2732e9dSimarom        self.term()
55b2732e9dSimarom
56b2732e9dSimarom    @classmethod
57b2732e9dSimarom    def shadow(cls, address):
58b2732e9dSimarom        """Shadow an existing libzmq context
59b2732e9dSimarom
60b2732e9dSimarom        address is the integer address of the libzmq context
61b2732e9dSimarom        or an FFI pointer to it.
62b2732e9dSimarom
63b2732e9dSimarom        .. versionadded:: 14.1
64b2732e9dSimarom        """
65b2732e9dSimarom        address = cast_int_addr(address)
66b2732e9dSimarom        return cls(shadow=address)
67b2732e9dSimarom
68b2732e9dSimarom    @classmethod
69b2732e9dSimarom    def shadow_pyczmq(cls, ctx):
70b2732e9dSimarom        """Shadow an existing pyczmq context
71b2732e9dSimarom
72b2732e9dSimarom        ctx is the FFI `zctx_t *` pointer
73b2732e9dSimarom
74b2732e9dSimarom        .. versionadded:: 14.1
75b2732e9dSimarom        """
76b2732e9dSimarom        from pyczmq import zctx
77b2732e9dSimarom
78b2732e9dSimarom        underlying = zctx.underlying(ctx)
79b2732e9dSimarom        address = cast_int_addr(underlying)
80b2732e9dSimarom        return cls(shadow=address)
81b2732e9dSimarom
82b2732e9dSimarom    # static method copied from tornado IOLoop.instance
83b2732e9dSimarom    @classmethod
84b2732e9dSimarom    def instance(cls, io_threads=1):
85b2732e9dSimarom        """Returns a global Context instance.
86b2732e9dSimarom
87b2732e9dSimarom        Most single-threaded applications have a single, global Context.
88b2732e9dSimarom        Use this method instead of passing around Context instances
89b2732e9dSimarom        throughout your code.
90b2732e9dSimarom
91b2732e9dSimarom        A common pattern for classes that depend on Contexts is to use
92b2732e9dSimarom        a default argument to enable programs with multiple Contexts
93b2732e9dSimarom        but not require the argument for simpler applications:
94b2732e9dSimarom
95b2732e9dSimarom            class MyClass(object):
96b2732e9dSimarom                def __init__(self, context=None):
97b2732e9dSimarom                    self.context = context or Context.instance()
98b2732e9dSimarom        """
99b2732e9dSimarom        if cls._instance is None or cls._instance.closed:
100b2732e9dSimarom            cls._instance = cls(io_threads=io_threads)
101b2732e9dSimarom        return cls._instance
102b2732e9dSimarom
103b2732e9dSimarom    #-------------------------------------------------------------------------
104b2732e9dSimarom    # Hooks for ctxopt completion
105b2732e9dSimarom    #-------------------------------------------------------------------------
106b2732e9dSimarom
107b2732e9dSimarom    def __dir__(self):
108b2732e9dSimarom        keys = dir(self.__class__)
109b2732e9dSimarom
110b2732e9dSimarom        for collection in (
111b2732e9dSimarom            ctx_opt_names,
112b2732e9dSimarom        ):
113b2732e9dSimarom            keys.extend(collection)
114b2732e9dSimarom        return keys
115b2732e9dSimarom
116b2732e9dSimarom    #-------------------------------------------------------------------------
117b2732e9dSimarom    # Creating Sockets
118b2732e9dSimarom    #-------------------------------------------------------------------------
119b2732e9dSimarom
120b2732e9dSimarom    @property
121b2732e9dSimarom    def _socket_class(self):
122b2732e9dSimarom        return Socket
123b2732e9dSimarom
124b2732e9dSimarom    def socket(self, socket_type):
125b2732e9dSimarom        """Create a Socket associated with this Context.
126b2732e9dSimarom
127b2732e9dSimarom        Parameters
128b2732e9dSimarom        ----------
129b2732e9dSimarom        socket_type : int
130b2732e9dSimarom            The socket type, which can be any of the 0MQ socket types:
131b2732e9dSimarom            REQ, REP, PUB, SUB, PAIR, DEALER, ROUTER, PULL, PUSH, etc.
132b2732e9dSimarom        """
133b2732e9dSimarom        if self.closed:
134b2732e9dSimarom            raise ZMQError(ENOTSUP)
135b2732e9dSimarom        s = self._socket_class(self, socket_type)
136b2732e9dSimarom        for opt, value in self.sockopts.items():
137b2732e9dSimarom            try:
138b2732e9dSimarom                s.setsockopt(opt, value)
139b2732e9dSimarom            except ZMQError:
140b2732e9dSimarom                # ignore ZMQErrors, which are likely for socket options
141b2732e9dSimarom                # that do not apply to a particular socket type, e.g.
142b2732e9dSimarom                # SUBSCRIBE for non-SUB sockets.
143b2732e9dSimarom                pass
144b2732e9dSimarom        return s
145b2732e9dSimarom
146b2732e9dSimarom    def setsockopt(self, opt, value):
147b2732e9dSimarom        """set default socket options for new sockets created by this Context
148b2732e9dSimarom
149b2732e9dSimarom        .. versionadded:: 13.0
150b2732e9dSimarom        """
151b2732e9dSimarom        self.sockopts[opt] = value
152b2732e9dSimarom
153b2732e9dSimarom    def getsockopt(self, opt):
154b2732e9dSimarom        """get default socket options for new sockets created by this Context
155b2732e9dSimarom
156b2732e9dSimarom        .. versionadded:: 13.0
157b2732e9dSimarom        """
158b2732e9dSimarom        return self.sockopts[opt]
159b2732e9dSimarom
160b2732e9dSimarom    def _set_attr_opt(self, name, opt, value):
161b2732e9dSimarom        """set default sockopts as attributes"""
162b2732e9dSimarom        if name in constants.ctx_opt_names:
163b2732e9dSimarom            return self.set(opt, value)
164b2732e9dSimarom        else:
165b2732e9dSimarom            self.sockopts[opt] = value
166b2732e9dSimarom
167b2732e9dSimarom    def _get_attr_opt(self, name, opt):
168b2732e9dSimarom        """get default sockopts as attributes"""
169b2732e9dSimarom        if name in constants.ctx_opt_names:
170b2732e9dSimarom            return self.get(opt)
171b2732e9dSimarom        else:
172b2732e9dSimarom            if opt not in self.sockopts:
173b2732e9dSimarom                raise AttributeError(name)
174b2732e9dSimarom            else:
175b2732e9dSimarom                return self.sockopts[opt]
176b2732e9dSimarom
177b2732e9dSimarom    def __delattr__(self, key):
178b2732e9dSimarom        """delete default sockopts as attributes"""
179b2732e9dSimarom        key = key.upper()
180b2732e9dSimarom        try:
181b2732e9dSimarom            opt = getattr(constants, key)
182b2732e9dSimarom        except AttributeError:
183b2732e9dSimarom            raise AttributeError("no such socket option: %s" % key)
184b2732e9dSimarom        else:
185b2732e9dSimarom            if opt not in self.sockopts:
186b2732e9dSimarom                raise AttributeError(key)
187b2732e9dSimarom            else:
188b2732e9dSimarom                del self.sockopts[opt]
189b2732e9dSimarom
190b2732e9dSimarom__all__ = ['Context']
191