Skip to content

Commit c3dac4a

Browse files
author
Dorian Birraux
committed
Merge pull request #221 in LCL/wolframclientforpython from feature/async-eval-remove-loop to master
* commit '516e260df3329354c9444daccc39edd289f3e5d8': (21 commits) drop 4 async generators. automatic code fix adding buffer types for force_text Use annotation on coroutine in parallele eval adding tests using dispatch to implement force_bytes and force_text adding comment for py2 adding a custom rule for memoryview fixing six need to add encoding for py2 simplify asyncio automatic code refactor update version update changelog update duplicated test Pool now use asyncio.run update utils asyncio Removing loop parameter in asynchronous evaluators Simplify parallel evaluate code. Minor rephrasing in pool code ...
2 parents feb0361 + 516e260 commit c3dac4a

20 files changed

Lines changed: 133 additions & 165 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Version 1.1.3
2+
- Update asynchronous evaluator classes. Remove the `loop` parameter. Most optional loop parameters are deprecated in the Python standart library `asyncio` and in most libraries, mainly because it is misleading and lead to misuses and bugs. The loop parameter is useful when instantiating asynchronous evaluators outside the scope of an event loop. It's implementation was not good enough and was relying on usages deprecated in 3.8.
3+
- Removing four asynchronous generators in asynchronous evaluation result classes: `iter_messages`, `iter_messages_name`, `iter_messages_tuple` and `iter_output`. These coroutines are only working on python 3.6+ and are not critical enough to drop support for 3.5. Asynchronous properties: `messages`, `messages_name` and `output` provide the same information.
4+
15
# Version 1.1.2
26
- Fix a backward incompatible change introduced in 12.1. Make sure the library works with any kernel version starting with 11.3.
37
- Minor changes and tweaks.

PacletInfo.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Paclet[
22
Name -> "WolframClientForPython",
3-
Version -> "1.1.2",
3+
Version -> "1.1.3",
44
MathematicaVersion -> "11.3+",
55
Loading -> Automatic,
66
Extensions -> {}

wolframclient/about.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
__name__ = "wolframclient"
44
__description__ = "A Python library with various tools to interact with the Wolfram Language and the Wolfram Cloud."
5-
__version__ = "1.1.2"
5+
__version__ = "1.1.3"
66
__author__ = "Wolfram Research"
77
__author_email__ = "support@wolfram.com, dorianb@wolfram.com, riccardod@wolfram.com"

wolframclient/deserializers/wxf/wxfparser.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,29 @@
1414

1515
wxf_input_to_buffer = Dispatch()
1616

17+
1718
@wxf_input_to_buffer.dispatch((six.binary_type, six.buffer_types))
1819
def encode_buffer(wxf_input):
1920
return six.BytesIO(wxf_input)
2021

22+
2123
if six.PY2:
22-
@wxf_input_to_buffer.dispatch(memoryview, replace_existing = True)
24+
25+
@wxf_input_to_buffer.dispatch(memoryview, replace_existing=True)
2326
def encode_buffer(wxf_input):
24-
return six.BytesIO(wxf_input.tobytes())
27+
return six.BytesIO(wxf_input.tobytes())
28+
2529

2630
@wxf_input_to_buffer.dispatch(object)
2731
def encode_default(wxf_input):
28-
if hasattr(wxf_input, 'read'):
32+
if hasattr(wxf_input, "read"):
2933
return wxf_input
3034
raise TypeError(
3135
"Class %s neither implements a read method nor is a binary type."
3236
% wxf_input.__class__.__name__
3337
)
3438

3539

36-
3740
class WXFParser(object):
3841
"""Parse a WXF input.
3942
@@ -80,7 +83,7 @@ def __init__(self, wxf_input):
8083
"""WXF parser returning Python object from a WXF encoded byte sequence.
8184
"""
8285
self.context = SerializationContext()
83-
self.reader = wxf_input_to_buffer(wxf_input)
86+
self.reader = wxf_input_to_buffer(wxf_input)
8487

8588
version, compress = self.parse_header()
8689
if compress == True:

wolframclient/evaluation/base.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -143,21 +143,20 @@ def __exit__(self, type, value, traceback):
143143

144144
class WolframAsyncEvaluator(WolframEvaluatorBase):
145145
""" Asynchronous evaluators are similar to synchronous ones except that they make heavy use of coroutines
146-
and need an event loop.
146+
and need to run in an event loop.
147147
148-
Most methods from this class are similar to their counterpart from :class:`~wolframclient.evaluation.base.WolframEvaluator`,
149-
except that they are coroutines. """
148+
Most methods from this class are similar to their counterpart from
149+
:class:`~wolframclient.evaluation.base.WolframEvaluator`, except that they are coroutines. """
150150

151-
def __init__(self, loop=None, **kwargs):
152-
self._loop = loop or asyncio.get_event_loop()
151+
def __init__(self, **kwargs):
153152
super().__init__(**kwargs)
154153

155154
async def evaluate(self, expr):
156155
result = await self.evaluate_wrap(expr)
157156
return await result.get()
158157

159158
async def evaluate_many(self, expr_list):
160-
return await asyncio.gather(*map(self.evaluate, expr_list), loop=self._loop)
159+
return await asyncio.gather(*map(self.evaluate, expr_list))
161160

162161
async def evaluate_wrap(self, expr):
163162
raise NotImplementedError
@@ -215,7 +214,13 @@ async def __aexit__(self, type, value, traceback):
215214

216215
def __del__(self, _warnings=warnings):
217216
super().__del__(_warnings=warnings)
218-
if not self.stopped and self._loop and not self._loop.is_closed():
219-
self._loop.call_exception_handler(
220-
{self.__class__.__name__: self, "message": "Unclosed evaluator."}
221-
)
217+
if not self.stopped:
218+
loop = None
219+
try:
220+
loop = asyncio.get_running_loop()
221+
except RuntimeError:
222+
pass
223+
if loop and not loop.is_closed():
224+
loop.call_exception_handler(
225+
{self.__class__.__name__: self, "message": "Unclosed evaluator."}
226+
)

wolframclient/evaluation/cloud/asynccloudsession.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ class WolframCloudAsyncSession(WolframAsyncEvaluator):
3838
async with WolframCloudAsyncSession() as session:
3939
await session.call(...)
4040
41-
An event loop can be explicitly passed using the named parameter `loop`; otherwise, the one
42-
returned by :func:`~asyncio.get_event_loop` is used.
4341
The initialization options of the class :class:`~wolframclient.evaluation.WolframCloudSession` are also supported by
4442
this class.
4543
"""
@@ -48,14 +46,13 @@ def __init__(
4846
self,
4947
credentials=None,
5048
server=None,
51-
loop=None,
5249
inputform_string_evaluation=True,
5350
oauth_session_class=None,
5451
xauth_session_class=None,
5552
http_sessionclass=None,
5653
ssl_context_class=None,
5754
):
58-
super().__init__(loop, inputform_string_evaluation=inputform_string_evaluation)
55+
super().__init__(inputform_string_evaluation=inputform_string_evaluation)
5956
self.server = server or WOLFRAM_PUBLIC_CLOUD_SERVER
6057
self.http_session = None
6158
self.http_sessionclass = http_sessionclass or aiohttp.ClientSession
@@ -76,7 +73,6 @@ def duplicate(self):
7673
return self.__class__(
7774
credentials=self.credentials,
7875
server=self.server,
79-
loop=self._loop,
8076
inputform_string_evaluation=self.inputform_string_evaluation,
8177
oauth_session_class=self.oauth_session_class,
8278
xauth_session_class=self.xauth_session_class,
@@ -90,7 +86,7 @@ async def start(self):
9086
if not self.started:
9187
if self.http_session is None or self.http_session.closed:
9288
self.http_session = self.http_sessionclass(
93-
headers={"User-Agent": "WolframClientForPython/1.0"}, loop=self._loop
89+
headers={"User-Agent": "WolframClientForPython/1.0"}
9490
)
9591
if not self.anonymous():
9692
await self._authenticate()

wolframclient/evaluation/cloud/oauth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ def authenticate(self):
202202
params = {
203203
"x_auth_username": self.xauth_credentials.user,
204204
"x_auth_password": self.xauth_credentials.password,
205-
"x_auth_mode": "client_auth"
205+
"x_auth_mode": "client_auth",
206206
}
207207

208208
# avoid dumping password in log files.

wolframclient/evaluation/kernel/asyncsession.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@ class WolframLanguageAsyncSession(WolframAsyncEvaluator, WolframLanguageSession)
2222
async with WolframLanguageAsyncSession() as session:
2323
await session.evaluate('Now')
2424
25-
An event loop can be explicitly passed using the named parameter `loop`; otherwise, the one
26-
returned by :func:`~asyncio.get_event_loop` is used.
27-
28-
Coroutines all run in a unique thread. Since a Wolfram kernel is single threaded, there can
25+
Coroutines all run in their own thread. Since a Wolfram kernel is single threaded, there can
2926
be only one evaluation at a time. In a sense, from the event loop point of view, evaluations
3027
are atomic operations. Even when many asynchronous sessions are started, the number of
3128
threads equals the number of kernel instances running and should not be problematic. Ensuring
@@ -37,7 +34,6 @@ def __init__(
3734
self,
3835
kernel=None,
3936
consumer=None,
40-
loop=None,
4137
initfile=None,
4238
kernel_loglevel=logging.NOTSET,
4339
stdin=PIPE,
@@ -55,14 +51,12 @@ def __init__(
5551
stdout=stdout,
5652
stderr=stderr,
5753
inputform_string_evaluation=inputform_string_evaluation,
58-
loop=loop,
5954
**kwargs
6055
)
6156

6257
def duplicate(self):
6358
return self.__class__(
6459
kernel=self.kernel,
65-
loop=self._loop,
6660
consumer=self.consumer,
6761
initfile=self.initfile,
6862
kernel_loglevel=self.kernel_loglevel,
@@ -77,7 +71,7 @@ async def do_evaluate_future(self, expr, result_update_callback=None, **kwargs):
7771
future = super().do_evaluate_future(
7872
expr, result_update_callback=result_update_callback, **kwargs
7973
)
80-
return asyncio.wrap_future(future, loop=self._loop)
74+
return asyncio.wrap_future(future)
8175

8276
async def evaluate_future(self, expr, **kwargs):
8377
await self.ensure_started()
@@ -138,7 +132,7 @@ async def start(self):
138132
139133
This method is a coroutine."""
140134
future = super().start_future()
141-
await asyncio.wrap_future(future, loop=self._loop)
135+
await asyncio.wrap_future(future)
142136

143137
async def stop(self):
144138
"""Asynchronously stop the session (graceful termination).
@@ -155,4 +149,4 @@ async def terminate(self):
155149
async def _async_terminate(self, gracefully):
156150
logger.info("Terminating asynchronous kernel session.")
157151
future = super().stop_future(gracefully=gracefully)
158-
await asyncio.wrap_future(future, loop=self._loop)
152+
await asyncio.wrap_future(future)

wolframclient/evaluation/kernel/kernelcontroller.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,8 +592,7 @@ def _cancel_tasks(self):
592592

593593
def __repr__(self):
594594
if self.started:
595-
return '<%s[%s ✅], "%s", pid:%i, kernel sockets: (in:%s, out:%s)>'\
596-
% (
595+
return '<%s[%s ✅], "%s", pid:%i, kernel sockets: (in:%s, out:%s)>' % (
597596
self.__class__.__name__,
598597
self.name,
599598
self.kernel,

wolframclient/evaluation/kernel/localsession.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,4 @@ def set_parameter(self, parameter_name, parameter_value):
283283
set_parameter.__doc__ = WolframKernelController.set_parameter.__doc__
284284

285285
def __repr__(self):
286-
return "<%s: %s>" % (
287-
self.__class__.__name__,
288-
self.kernel_controller,
289-
)
286+
return "<%s: %s>" % (self.__class__.__name__, self.kernel_controller)

0 commit comments

Comments
 (0)