1"""Subinterpreters High Level Module."""
2
3import time
4import _xxsubinterpreters as _interpreters
5
6# aliases:
7from _xxsubinterpreters import (
8    ChannelError, ChannelNotFoundError, ChannelEmptyError,
9    is_shareable,
10)
11
12
13__all__ = [
14    'Interpreter', 'get_current', 'get_main', 'create', 'list_all',
15    'SendChannel', 'RecvChannel',
16    'create_channel', 'list_all_channels', 'is_shareable',
17    'ChannelError', 'ChannelNotFoundError',
18    'ChannelEmptyError',
19    ]
20
21
22def create(*, isolated=True):
23    """Return a new (idle) Python interpreter."""
24    id = _interpreters.create(isolated=isolated)
25    return Interpreter(id, isolated=isolated)
26
27
28def list_all():
29    """Return all existing interpreters."""
30    return [Interpreter(id) for id in _interpreters.list_all()]
31
32
33def get_current():
34    """Return the currently running interpreter."""
35    id = _interpreters.get_current()
36    return Interpreter(id)
37
38
39def get_main():
40    """Return the main interpreter."""
41    id = _interpreters.get_main()
42    return Interpreter(id)
43
44
45class Interpreter:
46    """A single Python interpreter."""
47
48    def __init__(self, id, *, isolated=None):
49        if not isinstance(id, (int, _interpreters.InterpreterID)):
50            raise TypeError(f'id must be an int, got {id!r}')
51        self._id = id
52        self._isolated = isolated
53
54    def __repr__(self):
55        data = dict(id=int(self._id), isolated=self._isolated)
56        kwargs = (f'{k}={v!r}' for k, v in data.items())
57        return f'{type(self).__name__}({", ".join(kwargs)})'
58
59    def __hash__(self):
60        return hash(self._id)
61
62    def __eq__(self, other):
63        if not isinstance(other, Interpreter):
64            return NotImplemented
65        else:
66            return other._id == self._id
67
68    @property
69    def id(self):
70        return self._id
71
72    @property
73    def isolated(self):
74        if self._isolated is None:
75            # XXX The low-level function has not been added yet.
76            # See bpo-....
77            self._isolated = _interpreters.is_isolated(self._id)
78        return self._isolated
79
80    def is_running(self):
81        """Return whether or not the identified interpreter is running."""
82        return _interpreters.is_running(self._id)
83
84    def close(self):
85        """Finalize and destroy the interpreter.
86
87        Attempting to destroy the current interpreter results
88        in a RuntimeError.
89        """
90        return _interpreters.destroy(self._id)
91
92    def run(self, src_str, /, *, channels=None):
93        """Run the given source code in the interpreter.
94
95        This blocks the current Python thread until done.
96        """
97        _interpreters.run_string(self._id, src_str, channels)
98
99
100def create_channel():
101    """Return (recv, send) for a new cross-interpreter channel.
102
103    The channel may be used to pass data safely between interpreters.
104    """
105    cid = _interpreters.channel_create()
106    recv, send = RecvChannel(cid), SendChannel(cid)
107    return recv, send
108
109
110def list_all_channels():
111    """Return a list of (recv, send) for all open channels."""
112    return [(RecvChannel(cid), SendChannel(cid))
113            for cid in _interpreters.channel_list_all()]
114
115
116class _ChannelEnd:
117    """The base class for RecvChannel and SendChannel."""
118
119    def __init__(self, id):
120        if not isinstance(id, (int, _interpreters.ChannelID)):
121            raise TypeError(f'id must be an int, got {id!r}')
122        self._id = id
123
124    def __repr__(self):
125        return f'{type(self).__name__}(id={int(self._id)})'
126
127    def __hash__(self):
128        return hash(self._id)
129
130    def __eq__(self, other):
131        if isinstance(self, RecvChannel):
132            if not isinstance(other, RecvChannel):
133                return NotImplemented
134        elif not isinstance(other, SendChannel):
135            return NotImplemented
136        return other._id == self._id
137
138    @property
139    def id(self):
140        return self._id
141
142
143_NOT_SET = object()
144
145
146class RecvChannel(_ChannelEnd):
147    """The receiving end of a cross-interpreter channel."""
148
149    def recv(self, *, _sentinel=object(), _delay=10 / 1000):  # 10 milliseconds
150        """Return the next object from the channel.
151
152        This blocks until an object has been sent, if none have been
153        sent already.
154        """
155        obj = _interpreters.channel_recv(self._id, _sentinel)
156        while obj is _sentinel:
157            time.sleep(_delay)
158            obj = _interpreters.channel_recv(self._id, _sentinel)
159        return obj
160
161    def recv_nowait(self, default=_NOT_SET):
162        """Return the next object from the channel.
163
164        If none have been sent then return the default if one
165        is provided or fail with ChannelEmptyError.  Otherwise this
166        is the same as recv().
167        """
168        if default is _NOT_SET:
169            return _interpreters.channel_recv(self._id)
170        else:
171            return _interpreters.channel_recv(self._id, default)
172
173
174class SendChannel(_ChannelEnd):
175    """The sending end of a cross-interpreter channel."""
176
177    def send(self, obj):
178        """Send the object (i.e. its data) to the channel's receiving end.
179
180        This blocks until the object is received.
181        """
182        _interpreters.channel_send(self._id, obj)
183        # XXX We are missing a low-level channel_send_wait().
184        # See bpo-32604 and gh-19829.
185        # Until that shows up we fake it:
186        time.sleep(2)
187
188    def send_nowait(self, obj):
189        """Send the object to the channel's receiving end.
190
191        If the object is immediately received then return True
192        (else False).  Otherwise this is the same as send().
193        """
194        # XXX Note that at the moment channel_send() only ever returns
195        # None.  This should be fixed when channel_send_wait() is added.
196        # See bpo-32604 and gh-19829.
197        return _interpreters.channel_send(self._id, obj)
198