`_
11 | * bundled with Wolfram Language 12+
12 |
13 | Prerequisites
14 | ===============
15 |
16 | - Python 3.5 or higher
17 | - Wolfram Language 11.3 or higher
18 | - Git (optional)
19 |
20 |
21 | Installation
22 | ============================
23 |
24 | There are three methods for installing the library.
25 |
26 | Install Using `pip` (Recommended)
27 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28 |
29 | Recommended for most users. It installs the latest stable version released by Wolfram Research.
30 |
31 | Evaluate the following command in a terminal:
32 |
33 | .. code-block:: shell
34 |
35 | $ pip install wolframclient
36 |
37 | Install Using Git
38 | ^^^^^^^^^^^^^^^^^^^^^
39 |
40 | Recommended for developers who want to install the library along with the full source code.
41 |
42 | Clone the library's repository:
43 |
44 | .. code-block:: shell
45 |
46 | $ git clone git://github.com/WolframResearch/WolframClientForPython
47 |
48 | Install the library in your site-package directory:
49 |
50 | .. code-block:: shell
51 |
52 | $ pip install .
53 |
54 | Install from Wolfram Language--Based Products
55 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
56 |
57 | Future releases of the Wolfram Language will include a stable version of the client library. The library location depends on your OS. Here are the usual locations:
58 |
59 | +-----------+-------------------------------------------------------------------------------------------------------------------+
60 | | OS | Library path |
61 | +===========+===================================================================================================================+
62 | | *MacOS* | :file:`/Applications/Wolfram Desktop.app/Contents/SystemFiles/Components/WolframClientForPython` |
63 | +-----------+-------------------------------------------------------------------------------------------------------------------+
64 | | *Windows* | :file:`C:\\Program Files\\Wolfram Research\\Wolfram Desktop\\12\\SystemFiles\\Components\\WolframClientForPython` |
65 | +-----------+-------------------------------------------------------------------------------------------------------------------+
66 | | *Linux* | :file:`/usr/local/Wolfram/Desktop/12/SystemFiles/Components/WolframClientForPython` |
67 | +-----------+-------------------------------------------------------------------------------------------------------------------+
68 |
69 |
70 | From a terminal, evaluate the following commands:
71 |
72 | .. code-block:: shell
73 |
74 | $ cd /path/to/library
75 | $ pip install .
76 |
--------------------------------------------------------------------------------
/docs/docpages/toc.rst:
--------------------------------------------------------------------------------
1 | .. toctree::
2 | :maxdepth: 4
3 |
4 | ####################
5 | Table Of Contents
6 | ####################
7 |
8 | .. toctree::
9 | :maxdepth: 4
10 |
11 | install
12 | basic_usages
13 | advanced_usages
14 | public_api
15 | ../index
--------------------------------------------------------------------------------
/docs/docutils.conf:
--------------------------------------------------------------------------------
1 | [restructuredtext parser]
2 | syntax_highlight = short
--------------------------------------------------------------------------------
/docs/examples/python/asynchronous1.py:
--------------------------------------------------------------------------------
1 | from time import perf_counter
2 | from wolframclient.evaluation import WolframLanguageSession
3 |
4 | with WolframLanguageSession() as session:
5 | start = perf_counter()
6 | print('Starting an evaluation delayed by 2 seconds.')
7 | future = session.evaluate_future('Pause[2]; 1+1')
8 | print('After %.04fs, the code is running in the background, Python execution continues.' %
9 | (perf_counter()-start))
10 | # wait for up to 5 seconds.
11 | expr = future.result(timeout = 5)
12 | print('After %.02fs, result was available. Kernel evaluation returned: %s'
13 | % (perf_counter()-start, expr))
14 |
--------------------------------------------------------------------------------
/docs/examples/python/asynchronous2.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import time
3 | from wolframclient.evaluation import WolframLanguageAsyncSession
4 | from wolframclient.language import wl
5 |
6 | async def delayed_evaluation(delay, async_session, expr):
7 | await asyncio.sleep(delay)
8 | return await async_session.evaluate(expr)
9 |
10 | async def main():
11 | async with WolframLanguageAsyncSession() as async_session:
12 | start = time.perf_counter()
13 | print('Starting two tasks sequentially.')
14 | result1 = await delayed_evaluation(1, async_session, wl.Range(3))
15 | # Compute the Total of the previous evaluation result:
16 | result2 = await delayed_evaluation(1, async_session, wl.Total(result1))
17 | print('After %.02fs, both evaluations finished returning: %s, %s'
18 | % (time.perf_counter()-start, result1, result2))
19 |
20 | # python 3.5+
21 | loop = asyncio.get_event_loop()
22 | loop.run_until_complete(main())
23 |
24 | # python 3.7+
25 | # asyncio.run(main())
26 |
--------------------------------------------------------------------------------
/docs/examples/python/asynchronous3.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import time
3 | from wolframclient.evaluation import WolframLanguageAsyncSession
4 | from wolframclient.language import wl
5 |
6 | async def delayed_evaluation(delay, async_session, expr):
7 | await asyncio.sleep(delay)
8 | return await async_session.evaluate(expr)
9 |
10 | async def main():
11 | async with WolframLanguageAsyncSession() as async_session:
12 | start = time.perf_counter()
13 | print('Running two tasks concurrently.')
14 | task1 = asyncio.ensure_future(delayed_evaluation(1, async_session, '"hello"'))
15 | task2 = asyncio.ensure_future(delayed_evaluation(1, async_session, '"world!"'))
16 | # wait for the two tasks to finish
17 | result1 = await task1
18 | result2 = await task2
19 | print('After %.02fs, both evaluations finished returning: %s, %s'
20 | % (time.perf_counter()-start, result1, result2))
21 |
22 | # python 3.5+
23 | loop = asyncio.get_event_loop()
24 | loop.run_until_complete(main())
25 |
26 | # python 3.7+
27 | # asyncio.run(main())
28 |
--------------------------------------------------------------------------------
/docs/examples/python/asynchronous4.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import time
3 | from wolframclient.evaluation import WolframEvaluatorPool
4 |
5 | async def main():
6 | async with WolframEvaluatorPool() as pool:
7 | start = time.perf_counter()
8 | tasks = [
9 | pool.evaluate('Pause[1]')
10 | for i in range(10)
11 | ]
12 | await asyncio.wait(tasks)
13 | print('Done after %.02fs, using up to %i kernels.'
14 | % (time.perf_counter()-start, len(pool)))
15 |
16 | # python 3.5+
17 | loop = asyncio.get_event_loop()
18 | loop.run_until_complete(main())
19 |
20 | # python 3.7+
21 | # asyncio.run(main())
22 |
--------------------------------------------------------------------------------
/docs/examples/python/eigenvalues1.py:
--------------------------------------------------------------------------------
1 | from wolframclient.evaluation import WolframLanguageSession
2 | from wolframclient.language import wl
3 |
4 | with WolframLanguageSession() as session:
5 | # define a matrix of integers
6 | array = [
7 | [-1, 1, 1],
8 | [1, -1, 1],
9 | [1, 1, -1]]
10 | # expression to evaluate
11 | expr = wl.Eigenvalues(array)
12 | # send expression to the kernel for evaluation.
13 | res = session.evaluate(expr)
14 | print(res) # [-2, -2, 1]
15 |
--------------------------------------------------------------------------------
/docs/examples/python/eigenvalues2.py:
--------------------------------------------------------------------------------
1 | from wolframclient.evaluation import WolframLanguageSession
2 | from wolframclient.language import wl
3 | from wolframclient.deserializers import WXFConsumer, binary_deserialize
4 |
5 | class ComplexFunctionConsumer(WXFConsumer):
6 | """Implement a consumer that maps Complex to python complex types."""
7 |
8 | # represent the symbol Complex as a Python class
9 | Complex = wl.Complex
10 |
11 | def build_function(self, head, args, **kwargs):
12 | # return a built in complex if head is Complex and argument length is 2.
13 | if head == self.Complex and len(args) == 2:
14 | return complex(*args)
15 | # otherwise delegate to the super method (default case).
16 | else:
17 | return super().build_function(head, args, **kwargs)
18 |
19 | with WolframLanguageSession() as session:
20 | array = [
21 | [0, -2, 0],
22 | [1, 0, -1],
23 | [0, 2, 0]]
24 | # expression to evaluate
25 | expr = wl.Eigenvalues(array)
26 | # send expression to the kernel for evaluation.
27 | wxf = session.evaluate_wxf(expr)
28 | # get the WXF bytes and parse them using the complex consumer:
29 | complex_result = binary_deserialize(
30 | wxf,
31 | consumer=ComplexFunctionConsumer())
32 | print(complex_result) # [2j, -2j, 0]
33 |
--------------------------------------------------------------------------------
/docs/examples/python/eigenvalues3.py:
--------------------------------------------------------------------------------
1 | from wolframclient.evaluation import WolframLanguageSession
2 | from wolframclient.language import wl
3 | from wolframclient.deserializers import WXFConsumer, binary_deserialize
4 |
5 | import math
6 | import fractions
7 |
8 | # define Complex symbol once and for all
9 | Complex = wl.Complex
10 |
11 | class MathConsumer(WXFConsumer):
12 | """Implement a consumer with basic arithmetic operation."""
13 |
14 | # Specific convertion for Pi, other symbols use the default method.
15 | def consume_symbol(self, current_token, tokens, **kwargs):
16 | # Convert symbol Pi to its numeric value as defined in Python
17 | if current_token.data == 'Pi':
18 | return math.pi
19 | else:
20 | return super().consume_symbol(current_token, tokens, **kwargs)
21 |
22 | # Associate heads with the method to convert them to Python types.
23 | DISPATCH = {
24 | Complex: 'build_complex',
25 | wl.Rational: 'build_rational',
26 | wl.Plus: 'build_plus',
27 | wl.Times: 'build_times'
28 | }
29 | # Overload the method that builds functions.
30 | def build_function(self, head, args, **kwargs):
31 | # check if there is a specific function associated to the function head
32 | builder_func = self.DISPATCH.get(head, None)
33 | if builder_func is not None:
34 | try:
35 | # get the class method and apply it to the arguments.
36 | return getattr(self, builder_func)(*args)
37 | except Exception:
38 | # instead of failing, fallback to default case.
39 | return super().build_function(head, args, **kwargs)
40 | # heads not listed in DISPATCH are delegated to parent's method
41 | else:
42 | return super().build_function(head, args, **kwargs)
43 |
44 | def build_plus(self, *args):
45 | total = 0
46 | for arg in args:
47 | total = total + arg
48 | return total
49 |
50 | def build_times(self, *args):
51 | total = 1
52 | for arg in args:
53 | total = total * arg
54 | return total
55 |
56 | def build_rational(self, *args):
57 | if len(args) != 2:
58 | raise ValueError('Rational format not supported.')
59 | return fractions.Fraction(args[0], args[1])
60 |
61 | def build_complex(self, *args):
62 | if len(args) != 2:
63 | raise ValueError('Complex format not supported.')
64 | return complex(args[0], args[1])
65 |
66 | with WolframLanguageSession() as session:
67 | array = [
68 | [wl.Pi, -2, 0],
69 | [1, wl.Pi, -1],
70 | [0, 2, wl.Pi]]
71 |
72 | # expression to evaluate: Eigenvalues[array]
73 | expr = wl.Eigenvalues(array)
74 |
75 | # Eigenvalues are exact, but the result is a symbolic expression:
76 | # [Times[Rational[1, 2], Plus[Complex[0, 4], Times[2, Pi]]],
77 | # Times[Rational[1, 2], Plus[Complex[0, -4], Times[2, Pi]]], Pi]
78 | print(session.evaluate(expr))
79 |
80 | # Use evaluate_wxf to evaluate without deserializing the result.
81 | wxf = session.evaluate_wxf(expr)
82 | # deserialize using the math consumer:
83 | complex_result = binary_deserialize(wxf, consumer=MathConsumer())
84 | # get a numerical result, only made of built-in Python types.
85 | # [(3.141592653589793+2j), (3.141592653589793-2j), 3.141592653589793]
86 | print(complex_result)
87 |
--------------------------------------------------------------------------------
/docs/examples/python/eigenvalues3_alternative.py:
--------------------------------------------------------------------------------
1 | from wolframclient.evaluation import WolframLanguageSession
2 | from wolframclient.language import wl
3 | from wolframclient.deserializers import WXFConsumer, binary_deserialize
4 |
5 | # represent the symbol Complex as a Python class
6 | Complex = wl.Complex
7 |
8 | class ComplexFunctionConsumer(WXFConsumer):
9 | """Implement a consumer that maps Complex to python complex types."""
10 | def build_function(self, head, args, **kwargs):
11 | if head == Complex and len(args) == 2:
12 | return complex(*args)
13 | else:
14 | return super().build_function(head, args, **kwargs)
15 |
16 | with WolframLanguageSession() as session:
17 | array = [
18 | [wl.Pi, -2, 0],
19 | [1, wl.Pi, -1],
20 | [0, 2, wl.Pi]]
21 |
22 | # expression to evaluate: N[EigenValues[array]]
23 | expr = wl.N(wl.Eigenvalues(array))
24 |
25 | # evaluate without deserializing
26 | wxf = session.evaluate_wxf(expr)
27 | # deserialize using the math consumer:
28 | complex_result = binary_deserialize(wxf, consumer=ComplexFunctionConsumer())
29 | # [(3.141592653589793+2j), (3.141592653589793-2j), 3.141592653589793]
30 | print(complex_result)
--------------------------------------------------------------------------------
/docs/examples/python/encoder1.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.language import wl
4 | from wolframclient.serializers import export, wolfram_encoder
5 |
6 | # define a new class.
7 | class Animal(object):
8 | pass
9 |
10 | # register a new encoder for instances of the Animal class.
11 | @wolfram_encoder.dispatch(Animal)
12 | def encode_animal(serializer, animal):
13 | # encode the class as a symbol called Animal
14 | return serializer.encode(wl.Animal)
15 |
16 | # create a new instance
17 | animal = Animal()
18 | # serialize it
19 | result = export(animal)
20 | print(result) # b'Animal'
--------------------------------------------------------------------------------
/docs/examples/python/encoder2.py:
--------------------------------------------------------------------------------
1 | from wolframclient.language import wl
2 | from wolframclient.serializers import export, wolfram_encoder
3 |
4 | # define a hierarchy of classes.
5 | class Animal(object):
6 | pass
7 |
8 | class Fish(Animal):
9 | pass
10 |
11 | class Tuna(Fish):
12 | pass
13 |
14 | # will not have its own encoder.
15 | class Salmon(Fish):
16 | pass
17 |
18 | # register a new encoder for Animal.
19 | @wolfram_encoder.dispatch(Animal)
20 | def encode_animal(serializer, animal):
21 | return serializer.encode(wl.Animal)
22 |
23 | # register a new encoder for Fish.
24 | @wolfram_encoder.dispatch(Fish)
25 | def encode_fish(serializer, animal):
26 | return serializer.encode(wl.Fish)
27 |
28 | # register a new encoder for Tuna.
29 | @wolfram_encoder.dispatch(Tuna)
30 | def encode_tuna(serializer, animal):
31 | # encode the class as a function using class name
32 | return serializer.encode(wl.Tuna)
33 |
34 |
35 | expr = {'fish' : Fish(), 'tuna': Tuna(), 'salmon': Salmon()}
36 | result = export(expr)
37 | print(result) # b'<|"fish" -> Fish, "tuna" -> Tuna, "salmon" -> Fish|>'
--------------------------------------------------------------------------------
/docs/examples/python/globalcontext.py:
--------------------------------------------------------------------------------
1 | from wolframclient.evaluation import WolframLanguageSession
2 | from wolframclient.language import wl
3 | # conveniently import Global as g
4 | from wolframclient.language import Global as g
5 |
6 | with WolframLanguageSession() as session:
7 | # The function max belongs to context Global`
8 | session.evaluate('max[s : List[__String]] := MaximalBy[s, StringLength]')
9 | # Global`max is g.max in Python
10 | print(g.max)
11 | # Evaluate the function on a Python list of strings
12 | res = session.evaluate(g.max(['hello', 'darkness', 'my', 'old', 'friend']))
13 | print(res)
14 |
--------------------------------------------------------------------------------
/docs/examples/python/logging1.py:
--------------------------------------------------------------------------------
1 | from wolframclient.evaluation import WolframLanguageSession
2 | import logging
3 |
4 | # set the root level to INFO
5 | logging.basicConfig(level=logging.INFO)
6 |
7 | try:
8 | session = WolframLanguageSession()
9 | # this will trigger some log messages with the process ID, the sockets
10 | # address and the startup timer.
11 | session.start()
12 | # Warning: Infinite expression Power[0, -1] encountered.
13 | res = session.evaluate('1/0')
14 | finally:
15 | session.terminate()
16 |
--------------------------------------------------------------------------------
/docs/examples/python/logging2.py:
--------------------------------------------------------------------------------
1 | from wolframclient.evaluation import WolframLanguageSession
2 | import logging
3 |
4 | # set the Python root logger level to INFO
5 | logging.basicConfig(level=logging.INFO)
6 |
7 | # Start a new session, with kernel logging activated and log level set to INFO.
8 | with WolframLanguageSession(kernel_loglevel=logging.INFO) as session:
9 | # This message is printed
10 | session.evaluate('ClientLibrary`info["ON -- Example message printed from the kernel \
11 | with log level INFO --"]')
12 | # This one is not because its level is debug.
13 | session.evaluate('ClientLibrary`debug["OFF -- Debug message."]')
14 |
15 | # Disable logging from the kernel side
16 | session.evaluate('ClientLibrary`DisableKernelLogging[]')
17 | # These messages will not be sent to Python.
18 | session.evaluate('ClientLibrary`fatal["OFF -- Fatal message. Not printed"]')
19 | session.evaluate('ClientLibrary`info["OFF -- End of kernel evaluation. Not printed"]')
20 |
--------------------------------------------------------------------------------
/docs/examples/svg/ev3.svg:
--------------------------------------------------------------------------------
1 |
2 |
19 |
--------------------------------------------------------------------------------
/docs/examples/svg/piechart.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=python -msphinx
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 | set SPHINXPROJ=WolframClientLibraryForPython
13 |
14 | if "%1" == "" goto help
15 |
16 | %SPHINXBUILD% >NUL 2>NUL
17 | if errorlevel 9009 (
18 | echo.
19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
20 | echo.installed, then set the SPHINXBUILD environment variable to point
21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
22 | echo.may add the Sphinx directory to PATH.
23 | echo.
24 | echo.If you don't have Sphinx installed, grab it from
25 | echo.http://sphinx-doc.org/
26 | exit /b 1
27 | )
28 |
29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
30 | goto end
31 |
32 | :help
33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
34 |
35 | :end
36 | popd
37 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | Pygments==2.2
2 | pygments-mathematica==0.3.3
--------------------------------------------------------------------------------
/docs/roles/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import absolute_import, print_function, unicode_literals
4 | from roles.wl import wl_reference_role
5 |
6 | def setup(app):
7 | app.add_role('wl', wl_reference_role)
8 | return
9 |
--------------------------------------------------------------------------------
/docs/roles/wl.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import absolute_import, print_function, unicode_literals
4 | from docutils import nodes, utils
5 | from docutils.parsers.rst import roles
6 |
7 | wl_ref_url = 'http://reference.wolfram.com/language/ref/%s.html'
8 | wxf_ref_url = 'http://reference.wolfram.com/language/tutorial/WXFFormatDescription.html'
9 |
10 | def wl_reference_role(role_name, rawtext, wl_symbol, lineno, inliner,
11 | options={}, content=[]):
12 | if wl_symbol == 'WXF':
13 | symbol_ref_url = wxf_ref_url
14 | else:
15 | symbol_ref_url = wl_ref_url % utils.unescape(wl_symbol)
16 | node = nodes.reference(rawtext, wl_symbol, refuri=symbol_ref_url,
17 | classes=['wl', 'external'],
18 | **options)
19 | return [node], []
20 |
--------------------------------------------------------------------------------
/docs/templates/globaltoc.html:
--------------------------------------------------------------------------------
1 |
5 | {{ toctree(collapse=False, maxdepth=2, includehidden=True) }}
6 |
--------------------------------------------------------------------------------
/docs/templates/layout.html:
--------------------------------------------------------------------------------
1 | {% extends "!layout.html" %}
2 |
3 | {% block linktags %}
4 |
5 | {% if pagename == 'index' %}
6 |
7 | {% endif %}
8 | {% endblock %}
9 |
10 | {% set reldelim2 = "|" %}
11 |
12 | {% block relbar1 %}
13 |
65 | {% endblock %}
66 |
67 | {% block relbar2 %}
68 | {% endblock %}
69 |
70 | {% block footer %}
71 | {% endblock %}
--------------------------------------------------------------------------------
/docs/templates/sidecopyright.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/wri_theme/static/mma.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016 rsmenon
3 | * Licensed under the MIT License (https://opensource.org/licenses/MIT)
4 | */
5 | /*
6 | dorianb: Update colors using the Wolfram Cloud color scheme.
7 | */
8 |
9 | $light-gray: rgb(153, 153, 153);
10 | $dark-gray: rgb(102, 102, 102);
11 | $black: #000;
12 | $magenta: rgb(221, 17, 0);
13 | $blue: rgb(0, 44, 195);
14 | $green: rgb(67, 137, 88);
15 |
16 | .highlight-wl .highlight,
17 | .wlcode,
18 | .language-mathematica,
19 | .language-mma,
20 | .language-nb,
21 | .language-wl,
22 | .language-wolfram,
23 | .language-wolfram-language {
24 | .c { // Comment
25 | color: $light-gray;
26 | font-style: italic;
27 | }
28 | .nb { // Builtins
29 | color: $black;
30 | }
31 | .ne { // Messages (the "foo" in General::foo)
32 | color: $black;
33 | }
34 | .nf { // Slots
35 | color: $green;
36 | font-style: italic;
37 | }
38 | .nt { // Patterns
39 | color: $green;
40 | font-style: italic;
41 | }
42 | .nv { // Non-builtin symbols
43 | color: $blue;
44 | }
45 | .o { // Operators
46 | color: $black;
47 | }
48 | .m { // Numbers
49 | color: $black;
50 | }
51 | .p { // Groupings
52 | color: $black;
53 | }
54 | .s { // Strings
55 | color: $dark-gray;
56 | }
57 | .vc { // Local scope variables
58 | color: $green;
59 | }
60 | .err { // Any unrecognized input that is not lexed correctly
61 | color: $dark-gray;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/docs/wri_theme/static/python-client-library-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/docs/wri_theme/static/python-client-library-logo.png
--------------------------------------------------------------------------------
/docs/wri_theme/static/wolf-python-arrowleft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/docs/wri_theme/static/wolf-python-arrowleft.png
--------------------------------------------------------------------------------
/docs/wri_theme/static/wolf-python-arrowright.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/docs/wri_theme/static/wolf-python-arrowright.png
--------------------------------------------------------------------------------
/docs/wri_theme/static/wolf-python-homepage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/docs/wri_theme/static/wolf-python-homepage.png
--------------------------------------------------------------------------------
/docs/wri_theme/static/wolf-python-subpage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/docs/wri_theme/static/wolf-python-subpage.png
--------------------------------------------------------------------------------
/docs/wri_theme/static/wri-index.css:
--------------------------------------------------------------------------------
1 | header.main-header {
2 | height: 0px;
3 | padding-top: 0px;
4 | }
5 | div.document {
6 | top:4px;
7 | }
8 |
9 | div.body {
10 | padding-left: 35px
11 | }
12 |
13 | div.document-header {
14 | padding-top: 60px;
15 | padding-bottom: 35px;
16 | }
17 |
18 | div.document-header div.header-content {
19 | font-size: 2.5em;
20 | display: table-row;
21 | }
22 |
23 | div.document-header div.left {
24 | width: 80%;
25 | margin-right:20%;
26 | display:table-cell;
27 | }
28 |
29 | div.document-header div.right {
30 | width: 20%;
31 | display:table-cell;
32 | vertical-align: middle
33 | }
34 |
35 | div.document-header div.right img.align-right {
36 | margin-left:0;
37 | max-width: 100%;
38 | width: auto;
39 | }
40 |
41 | div.document-subheader {
42 | font-size: 1.4em;
43 | border-top: 1px solid #ccc;
44 | padding-top:15px;
45 | padding-bottom:10px;
46 | }
47 |
48 | div.highlight-console pre {
49 | background-color: #f2f2f2 !important;
50 | }
51 | div.highlight-console span.gp {
52 | color : inherit !important;
53 | }
54 |
55 | h3 {
56 | color: #3c3c3c; /* darkgrey */
57 | font-weight: 700;
58 | font-size: 1.5em;
59 | }
60 |
61 | .border-top {
62 | border-top: 1px solid #ccc;
63 | }
64 |
65 | .bodyaligned {
66 | margin-left : 15px;
67 | }
68 |
69 | div#source-code{
70 | margin-top:10px;
71 | font-size:1.4em;
72 |
73 | }
74 |
75 | div#source-code svg {
76 | vertical-align: text-bottom;
77 | height: 30px;
78 | width: 30px;
79 | }
80 |
81 | span#header-title-top {
82 | font-size:1em;
83 | color:#3c3c3c;
84 | line-height:.6;
85 | }
86 | span#header-title-bottom {
87 | font-size:1.2em;
88 | color:#1667b2;
89 | }
90 | span#header-subtitle {
91 | color: #9c9c9c;
92 | font-size:.55em;
93 | font-weight:inherit;
94 | font-style:italic;
95 | }
96 | span#header-summary {
97 | color: #7a7a7a;
98 | }
99 | div#install-lib{
100 | color: #404040;
101 | font-size: 1.3em;
102 | font-weight: 700;
103 | margin-top: 30px;
104 | }
105 | div#setup-session {
106 | color: #7a7a7a;
107 | font-weight: 700;
108 | padding-top: 10px;
109 | padding-bottom: 5px;
110 | margin-top: 50px;
111 | }
112 |
113 | div.code-label {
114 | font-size: .9em;
115 | font-style: italic;
116 | }
117 | div.subsection {
118 | padding-top: 10px;
119 | padding-bottom: 5px;
120 | margin-bottom: 10px;
121 | margin-top: 50px;
122 | font-size: 1.1em;
123 | font-weight: 700;
124 | }
125 |
126 | /* div.annotated {
127 | margin-bottom: 40px;
128 | margin-top: 50px;
129 | } */
130 |
131 | div#letsyou-section {
132 | color: #1667b2;
133 | font-size: 1.85em;
134 | margin-top: 70px;
135 | }
136 |
137 | div#wl-references-list ul{
138 | list-style: none;
139 | line-height:inherit;
140 | position: relative;
141 | top : -10px;
142 | padding-left:0px;
143 | height: 10px;
144 | }
145 | div#wl-references-list li {
146 | float:left;
147 | margin-left:0px;
148 | }
149 |
150 | div#wl-references-list a {
151 | text-decoration:none;
152 | }
153 |
154 | span.inlinebullet {
155 | color : #adadad;
156 | padding-left: 10px;
157 | padding-right: 10px;
158 | }
--------------------------------------------------------------------------------
/docs/wri_theme/theme.conf:
--------------------------------------------------------------------------------
1 | [theme]
2 | inherit = default
3 | stylesheet = wri.css
4 | pygments_style = pygments.css
5 |
6 |
--------------------------------------------------------------------------------
/run.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import absolute_import, print_function, unicode_literals
4 |
5 | if __name__ == '__main__':
6 |
7 | #this will perform an auto install of missing modules using PIP
8 | #this should not be used in production, but it's handy when we are giving this paclet to other developers
9 | #as it provides convenient access to unit tests, profiler, and benchmarking.
10 |
11 | from wolframclient.cli.dispatch import execute_from_command_line
12 |
13 | execute_from_command_line()
--------------------------------------------------------------------------------
/scripts/re_build_WolframClient.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
66 |
67 |
68 |
69 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | universal = 1
3 |
4 | [metadata]
5 | license_file = LICENSE
6 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import sys
4 | import os
5 | # used for the sake of Python2 and 3 compatibility.
6 | import codecs
7 |
8 | try:
9 | from setuptools import setup, find_packages
10 | except ImportError:
11 | sys.stderr.write("""Could not import setuptools or your version
12 | of the package is out of date.
13 |
14 | Make sure you have pip and setuptools installed and upgraded and try again:
15 | $ python -m pip install --upgrade pip setuptools
16 | $ python setup.py install
17 |
18 | """)
19 |
20 | HERE = os.path.abspath(os.path.dirname(__file__))
21 |
22 | CLASSIFIERS = [
23 | "License :: OSI Approved :: MIT License",
24 | "Programming Language :: Python",
25 | "Programming Language :: Python :: 3",
26 | "Programming Language :: Python :: 3.5",
27 | "Programming Language :: Python :: 3.6",
28 | "Programming Language :: Python :: 3.7",
29 | "Topic :: Software Development :: Libraries"
30 | ]
31 |
32 |
33 | def read(*rellibpath):
34 | with codecs.open(os.path.join(HERE, *rellibpath), 'r', encoding='utf-8') as fp:
35 | return fp.read()
36 |
37 | def load_tests():
38 | import unittest
39 | from wolframclient.cli.commands.test import Command as TestCommand
40 | TestCommand().handle()
41 |
42 | ABOUT = {}
43 | with open(os.path.join(HERE, 'wolframclient', 'about.py'), 'r') as fp:
44 | exec(fp.read(), ABOUT)
45 |
46 |
47 | setup(
48 | name = ABOUT['__name__'],
49 | version = ABOUT['__version__'],
50 | description = ABOUT['__description__'],
51 | long_description = read('README.rst'),
52 | long_description_content_type = 'text/x-rst',
53 | keywords=['Wolfram Language', 'Wolfram Desktop', 'Mathematica', 'parser', 'serializer', 'WXF'],
54 | author = ABOUT['__author__'],
55 | author_email = ABOUT['__author_email__'],
56 | license = 'MIT',
57 | url = 'https://www.wolfram.com/',
58 | include_package_data=True,
59 | packages=find_packages(),
60 | test_suite='setup.load_tests',
61 | python_requires='>=3.5.3',
62 | install_requires = [
63 | 'numpy',
64 | 'pytz',
65 | 'requests',
66 | 'aiohttp',
67 | 'oauthlib',
68 | 'pyzmq',
69 | 'certifi>=2017.4.17' # for consistency with requests module.
70 | ],
71 | classifiers = CLASSIFIERS,
72 | project_urls={
73 | 'Source code': 'https://github.com/WolframResearch/WolframClientForPython',
74 | 'Documentation': 'https://wolfr.am/wolframclientdoc',
75 | 'Wolfram Research': 'https://www.wolfram.com',
76 | },
77 | entry_points = {
78 | 'wolframclient_serializers_encoder':[]
79 | }
80 | )
81 |
--------------------------------------------------------------------------------
/wolframclient/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.about import __author__, __name__, __version__
4 |
5 | __all__ = ("__version__", "__name__", "__author__")
6 |
--------------------------------------------------------------------------------
/wolframclient/__main__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.cli.dispatch import execute_from_command_line as main
4 |
5 | if __name__ == "__main__":
6 | main()
7 |
--------------------------------------------------------------------------------
/wolframclient/about.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | __name__ = "wolframclient"
4 | __description__ = "A Python library with various tools to interact with the Wolfram Language and the Wolfram Cloud."
5 | __version__ = "1.4.0"
6 | __author__ = "Wolfram Research"
7 | __author_email__ = "support@wolfram.com, dorianb@wolfram.com, riccardod@wolfram.com"
8 |
--------------------------------------------------------------------------------
/wolframclient/cli/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
--------------------------------------------------------------------------------
/wolframclient/cli/commands/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
--------------------------------------------------------------------------------
/wolframclient/cli/commands/refactor.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import os
4 | import subprocess
5 | import sys
6 | from functools import partial
7 |
8 | from wolframclient.cli.utils import SimpleCommand
9 | from wolframclient.utils.decorators import to_tuple
10 | from wolframclient.utils.functional import flatten, iterate
11 | from wolframclient.utils.importutils import module_path
12 |
13 | process_verbose = partial(
14 | subprocess.Popen,
15 | stdout=sys.stdout,
16 | stdin=subprocess.PIPE,
17 | stderr=sys.stderr,
18 | env=os.environ,
19 | )
20 |
21 |
22 | @to_tuple
23 | def wait_for_process(processes, raise_errors=True, show_output=False):
24 | for p in iterate(processes):
25 | if raise_errors and not p.wait() == 0:
26 | raise ValueError("Process finished with non zero status:")
27 |
28 | p.wait()
29 | yield p
30 |
31 |
32 | class Command(SimpleCommand):
33 |
34 | modules = ["wolframclient"]
35 |
36 | @to_tuple
37 | def _process_args(self, repo, pre, *args):
38 | yield sys.executable
39 | yield "-m"
40 | yield from iterate(pre)
41 | yield module_path(repo)
42 | yield from args
43 |
44 | def run(self, pre, *args):
45 | args = tuple(flatten(args))
46 | args = tuple(self._process_args(repo, pre, *args) for repo in self.modules)
47 |
48 | for a in args:
49 | print(" ".join(a))
50 |
51 | return wait_for_process(map(process_verbose, args), raise_errors=False)
52 |
53 | def handle(self, **opts):
54 | # self.run(("ruff", "format"), "--target-version", "py311")
55 | # to do import replacement do python -m isort Git/rotostampa --force-single-line-imports
56 |
57 | self.run(
58 | ("ruff", "check"),
59 | "--fix",
60 | "--unsafe-fixes",
61 | "--select",
62 | "ALL",
63 | (
64 | ("--ignore", r)
65 | for r in (
66 | "ANN001",
67 | "ANN002",
68 | "ANN003",
69 | "ANN101",
70 | "ANN102",
71 | "ANN201",
72 | "ANN202",
73 | "ANN204",
74 | "ANN205",
75 | "ANN206",
76 | "ANN401",
77 | "B006",
78 | "COM812",
79 | "D100",
80 | "D101",
81 | "D102",
82 | "D103",
83 | "D104",
84 | "D105",
85 | "D106",
86 | "D107",
87 | "D200",
88 | "D201",
89 | "D202",
90 | "D203",
91 | "D204",
92 | "D205",
93 | "D206",
94 | "D207",
95 | "D208",
96 | "D209",
97 | "D210",
98 | "D211",
99 | "D212",
100 | "D213",
101 | "D214",
102 | "D215",
103 | "D300",
104 | "D301",
105 | "D400",
106 | "D401",
107 | "D402",
108 | "D403",
109 | "D404",
110 | "D405",
111 | "D406",
112 | "D407",
113 | "D408",
114 | "D409",
115 | "D410",
116 | "D411",
117 | "D412",
118 | "D413",
119 | "D414",
120 | "D415",
121 | "D416",
122 | "D417",
123 | "D418",
124 | "D419",
125 | "E501",
126 | "E731",
127 | "EM101",
128 | "EM102",
129 | "EM103",
130 | "RET502",
131 | "RET503",
132 | "UP032",
133 | "FLY002",
134 | "PT009",
135 | "SIM118",
136 | "PT027",
137 | "T201",
138 | "T203",
139 | "UP010",
140 | "SIM105",
141 | "UP009",
142 | "G010",
143 | "LOG009",
144 | )
145 | ),
146 | )
147 | self.run(
148 | "black",
149 | "--line-length",
150 | "95",
151 | "--target-version",
152 | "py311",
153 | "--skip-magic-trailing-comma",
154 | )
155 |
--------------------------------------------------------------------------------
/wolframclient/cli/commands/setup.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.cli.utils import SimpleCommand
4 | from wolframclient.utils import six
5 | from wolframclient.utils.decorators import to_tuple
6 |
7 |
8 | @to_tuple
9 | def dependencies():
10 | yield "pytz"
11 |
12 | if not six.PY2:
13 | yield "aiohttp"
14 | yield "numpy"
15 | yield "oauthlib"
16 | yield "pandas"
17 | yield "pillow"
18 | yield "pyzmq"
19 | yield "requests"
20 | yield "unittest-xml-reporting"
21 | yield "certifi>=2017.4.17"
22 |
23 |
24 | class Command(SimpleCommand):
25 | """Run test suites from the tests modules.
26 | A list of patterns can be provided to specify the tests to run.
27 | """
28 |
29 | dependencies = dependencies()
30 |
--------------------------------------------------------------------------------
/wolframclient/cli/commands/start_externalevaluate.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import os
4 | import sys
5 |
6 | from wolframclient.cli.utils import SimpleCommand
7 | from wolframclient.utils.api import externalevaluate as ev
8 | from wolframclient.utils.api import zmq
9 |
10 |
11 | class Command(SimpleCommand):
12 |
13 | dependencies = ()
14 |
15 | def add_arguments(self, parser):
16 | parser.add_argument("--port", dest="port", default=None)
17 | parser.add_argument("--installpath", dest="installpath", default=None)
18 | parser.add_argument("--kernelversion", dest="kernelversion", default=None)
19 |
20 | def handle(self, port=None, installpath=None, kernelversion=None, **opts):
21 |
22 | for key, value in (
23 | ("WOLFRAM_INSTALLATION_DIRECTORY", installpath),
24 | ("WOLFRAM_KERNEL_VERSION", kernelversion),
25 | ):
26 | if value:
27 | os.environ[key] = value
28 |
29 | try:
30 | zmq.Context
31 | except ImportError as e:
32 | print(
33 | 'Error importing zmq: {}. Please install zmq by running:\nExternalEvaluate[{{"Shell", "Target" :> $SystemShell}}, "{}" -> {{"-m", "pip", "install", "pyzmq", "--user", "--no-input"}}]'.format(
34 | e, sys.executable
35 | ),
36 | file=sys.stderr,
37 | )
38 | sys.stderr.flush()
39 | sys.exit(1)
40 |
41 | ev.start_zmq_loop(port=port)
42 |
--------------------------------------------------------------------------------
/wolframclient/cli/commands/test.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import sys
4 | import unittest
5 |
6 | from wolframclient.cli.utils import SimpleCommand
7 | from wolframclient.utils.functional import map
8 | from wolframclient.utils.importutils import module_path
9 |
10 |
11 | class Command(SimpleCommand):
12 | """Run test suites from the tests modules.
13 | A list of patterns can be provided to specify the tests to run.
14 | """
15 |
16 | modules = ["wolframclient.tests"]
17 |
18 | def add_arguments(self, parser):
19 | parser.add_argument(
20 | "-x",
21 | "--xml",
22 | dest="produce_xml",
23 | action="store_true",
24 | help="produce xml reports from the test results",
25 | )
26 | parser.add_argument(
27 | "-d",
28 | "--xml-dir",
29 | dest="xml_output_dir",
30 | help="produce an xml report in a specific directory.",
31 | )
32 | parser.add_argument(
33 | "-v",
34 | "--verbosity",
35 | type=int,
36 | choices=[0, 1, 2],
37 | default=2,
38 | help="set output verbosity",
39 | )
40 | parser.add_argument("args", nargs="*")
41 |
42 | def handle(self, *args, **opts):
43 |
44 | suite = unittest.TestSuite()
45 | for root in map(module_path, self.modules):
46 | for arg in args or ["*"]:
47 | suite.addTests(
48 | unittest.defaultTestLoader.discover(root, pattern=arg, top_level_dir=root)
49 | )
50 |
51 | # verbosity > 1 print test name
52 | verbosity = opts.get("verbosity")
53 | # for consistency with parser, set default to 2. This is only possible when calling test from setup.py
54 | if verbosity is None:
55 | verbosity = 2
56 | xml_path = opts.get("xml_output_dir")
57 | # if opts.get('produce_xml'):
58 | if xml_path is not None or opts.get("produce_xml"):
59 | import xmlrunner
60 |
61 | runner = xmlrunner.XMLTestRunner(
62 | output=xml_path or "test-reports", verbosity=verbosity
63 | )
64 | else:
65 | runner = unittest.TextTestRunner(verbosity=verbosity)
66 |
67 | result = runner.run(suite)
68 |
69 | if not result.wasSuccessful():
70 | sys.exit(1)
71 |
--------------------------------------------------------------------------------
/wolframclient/cli/dispatch.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import sys
4 |
5 | from wolframclient.cli.utils import SimpleCommand, discover_with_convention
6 | from wolframclient.utils.importutils import import_string
7 | from wolframclient.utils.require import require_module
8 |
9 |
10 | class DispatchCommand(SimpleCommand):
11 |
12 | modules = ["wolframclient.cli.commands"]
13 | class_name = "Command"
14 |
15 | default_command = None
16 |
17 | dependencies = ()
18 |
19 | def subcommands(self):
20 | return discover_with_convention(self.modules, self.class_name)
21 |
22 | def handle(self, attr=None):
23 |
24 | all_commands = self.subcommands()
25 |
26 | if attr is None and self.default_command:
27 | attr = self.default_command
28 |
29 | if attr in all_commands:
30 | return import_string(all_commands[attr])(
31 | self.subcommand_args(), name=all_commands[attr]
32 | ).main()
33 |
34 | self.print("Select one of the following commands:")
35 | for command in sorted(all_commands.keys()):
36 | self.print(" -", command)
37 |
38 | sys.exit(1)
39 |
40 | def subcommand_args(self):
41 | argv = list(self.argv)
42 | if len(argv) > 1:
43 | argv.pop(1)
44 | return argv
45 |
46 | def main(self):
47 |
48 | if self.dependencies:
49 | require_module(*self.dependencies)
50 |
51 | if len(self.argv) > 1 and self.argv[1]:
52 | return self.handle(self.argv[1])
53 | return self.handle()
54 |
55 |
56 | def execute_from_command_line(argv=None, **opts):
57 | return DispatchCommand(argv).main()
58 |
--------------------------------------------------------------------------------
/wolframclient/cli/utils.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import argparse
4 | import os
5 | import sys
6 |
7 | from wolframclient.utils.decorators import to_dict
8 | from wolframclient.utils.importutils import module_path
9 | from wolframclient.utils.require import require_module
10 |
11 | if hasattr(os, "scandir"):
12 | # python2 do not support scan which is way faster
13 | # the function was introduced in py3.5, so it's better to just check if the function is there using hasattr.
14 |
15 | def _scan(folder):
16 | for f in os.scandir(folder):
17 | yield f.is_dir(), f.name
18 |
19 | else:
20 |
21 | def _scan(folder):
22 | for f in os.listdir(folder):
23 | yield os.path.isdir(os.path.join(folder, f)), f
24 |
25 |
26 | def _discover(module, folder=None, walk=True):
27 | folder = folder or module_path(module)
28 | for is_folder, filename in _scan(folder):
29 | if not is_folder:
30 | yield module, filename
31 | elif walk and filename != "__pycache__":
32 | yield from _discover(
33 | "{}.{}".format(module, filename),
34 | folder=os.path.join(folder, filename),
35 | walk=walk,
36 | )
37 |
38 |
39 | @to_dict
40 | def discover_with_convention(modules, import_name, walk=True):
41 | for module in modules:
42 | for module, filename in _discover(module, walk=walk):
43 | basename, ext = os.path.splitext(filename)
44 | if ext == ".py" and basename != "__init__":
45 | yield basename, "{}.{}.{}".format(module, basename, import_name)
46 |
47 |
48 | class SimpleCommand:
49 |
50 | help = None
51 | print = print
52 |
53 | dependencies = ()
54 |
55 | def __init__(self, argv=None, name=None):
56 | if argv is None:
57 | self.argv = tuple(sys.argv)
58 | else:
59 | self.argv = argv
60 |
61 | self.name = name
62 |
63 | def create_parser(self):
64 | return argparse.ArgumentParser(prog=self.name, description=self.help)
65 |
66 | def add_arguments(self, parser):
67 | pass
68 |
69 | def handle(self, *args, **opts):
70 | pass
71 |
72 | def main(self):
73 |
74 | if self.dependencies:
75 | require_module(*self.dependencies)
76 |
77 | parser = self.create_parser()
78 | if parser:
79 | self.add_arguments(parser)
80 |
81 | cmd_options = vars(parser.parse_args(self.argv[1:]))
82 | args = cmd_options.pop("args", ())
83 | return self.handle(*args, **cmd_options)
84 |
85 | return self.handle()
86 |
--------------------------------------------------------------------------------
/wolframclient/deserializers/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.deserializers.wxf import binary_deserialize
4 | from wolframclient.deserializers.wxf.wxfconsumer import WXFConsumer, WXFConsumerNumpy
5 | from wolframclient.deserializers.wxf.wxfparser import WXFToken
6 |
7 | __all__ = ["WXFConsumer", "WXFToken", "binary_deserialize", "WXFConsumer", "WXFConsumerNumpy"]
8 |
--------------------------------------------------------------------------------
/wolframclient/deserializers/wxf/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.deserializers.wxf.wxfconsumer import WXFConsumerNumpy
4 | from wolframclient.deserializers.wxf.wxfparser import WXFParser
5 | from wolframclient.exception import WolframParserException
6 |
7 | __all__ = ["binary_deserialize"]
8 |
9 |
10 | def binary_deserialize(wxf_input, consumer=None, **kwargs):
11 | """Deserialize binary data and return a Python object.
12 |
13 | Serialize a Python object to WXF::
14 |
15 | >>> wxf = export({'key' : [1,2,3]}, target_format='wxf')
16 |
17 | Retrieve the input object::
18 |
19 | >>> binary_deserialize(wxf)
20 | {'key': [1, 2, 3]}
21 |
22 | A stream of :class:`~wolframclient.deserializers.wxf.wxfparser.WXFToken` is generated from the WXF input by a instance
23 | of :class:`~wolframclient.deserializers.wxf.wxfparser.WXFParser`.
24 |
25 | The consumer must be an instance of :class:`~wolframclient.deserializers.wxf.wxfconsumer.WXFConsumer`. If none is
26 | provided, :class:`~wolframclient.deserializers.wxf.wxfconsumer.WXFConsumerNumpy` is used. To disable NumPy array support,
27 | use :class:`~wolframclient.deserializers.wxf.wxfconsumer.WXFConsumer`.
28 |
29 | Named parameters are passed to the consumer. They can be any valid parameter of
30 | :meth:`~wolframclient.deserializers.wxf.wxfconsumer.WXFConsumer.next_expression`, namely:
31 |
32 | * `dict_class`: map WXF `Association` to `dict_class` in place of a regular :class:`dict`
33 |
34 | """
35 | parser = WXFParser(wxf_input)
36 | if consumer is None:
37 | consumer = WXFConsumerNumpy()
38 |
39 | try:
40 | o = consumer.next_expression(parser.tokens(), **kwargs)
41 | except StopIteration:
42 | raise WolframParserException(
43 | "Input data does not represent a valid expression in WXF format. Expecting more input data."
44 | )
45 | if not parser.context.is_valid_final_state():
46 | raise WolframParserException(
47 | "Input data does not represent a valid expression in WXF format. Some expressions are incomplete."
48 | )
49 | return o
50 |
--------------------------------------------------------------------------------
/wolframclient/evaluation/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.evaluation.cloud import (
4 | SecuredAuthenticationKey,
5 | UserIDPassword,
6 | WolframAPICall,
7 | WolframAPICallAsync,
8 | WolframCloudAsyncSession,
9 | WolframCloudSession,
10 | WolframServer,
11 | )
12 | from wolframclient.evaluation.kernel import WolframLanguageAsyncSession, WolframLanguageSession
13 | from wolframclient.evaluation.pool import WolframEvaluatorPool, parallel_evaluate
14 | from wolframclient.evaluation.result import (
15 | WolframAPIResponse,
16 | WolframAPIResponseAsync,
17 | WolframCloudEvaluationJSONResponse,
18 | WolframEvaluationJSONResponseAsync,
19 | WolframKernelEvaluationResult,
20 | WolframResult,
21 | )
22 |
23 | __all__ = [
24 | "SecuredAuthenticationKey",
25 | "UserIDPassword",
26 | "WolframAPICall",
27 | "WolframAPICall",
28 | "WolframAPICallAsync",
29 | "WolframAPIResponse",
30 | "WolframAPIResponseAsync",
31 | "WolframCloudAsyncSession",
32 | "WolframCloudEvaluationJSONResponse",
33 | "WolframCloudSession",
34 | "WolframEvaluationJSONResponseAsync",
35 | "WolframEvaluatorPool",
36 | "WolframKernelEvaluationResult",
37 | "WolframLanguageAsyncSession",
38 | "WolframLanguageSession",
39 | "WolframResult",
40 | "WolframServer",
41 | "parallel_evaluate",
42 | ]
43 |
--------------------------------------------------------------------------------
/wolframclient/evaluation/cloud/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.evaluation.cloud.asynccloudsession import (
4 | WolframAPICallAsync,
5 | WolframCloudAsyncSession,
6 | )
7 | from wolframclient.evaluation.cloud.base import SecuredAuthenticationKey, UserIDPassword
8 | from wolframclient.evaluation.cloud.cloudsession import WolframAPICall, WolframCloudSession
9 | from wolframclient.evaluation.cloud.server import WolframServer
10 |
11 | __all__ = [
12 | "WolframServer",
13 | "WolframCloudSession",
14 | "WolframAPICall",
15 | "SecuredAuthenticationKey",
16 | "UserIDPassword",
17 | "WolframAPICallAsync",
18 | "WolframCloudAsyncSession",
19 | ]
20 |
--------------------------------------------------------------------------------
/wolframclient/evaluation/cloud/request_adapter.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.utils.api import aiohttp, requests
4 | from wolframclient.utils.encoding import force_text
5 |
6 | __all__ = ["wrap_response"]
7 |
8 |
9 | class HTTPResponseAdapterBase:
10 | """Unify various request classes as a unique API."""
11 |
12 | asynchronous = False
13 |
14 | def __init__(self, httpresponse):
15 | self.response = httpresponse
16 |
17 | def response_object(self):
18 | return self.response
19 |
20 | def status(self):
21 | """HTTP status code"""
22 | return self.response.status_code
23 |
24 | def json(self):
25 | """Request body as a json object"""
26 | return self.response.json()
27 |
28 | def text(self):
29 | """Request body as decoded text."""
30 | return self.response.text
31 |
32 | def content(self):
33 | """Request body as raw bytes"""
34 | return self.response.content
35 |
36 | def url(self):
37 | """String URL."""
38 | return force_text(self.response.url)
39 |
40 | def headers(self):
41 | """Headers as a dict."""
42 | return self.response.headers
43 |
44 |
45 | class RequestsHTTPRequestAdapter(HTTPResponseAdapterBase):
46 | pass
47 |
48 |
49 | class AIOHttpHTTPRequestAdapter(HTTPResponseAdapterBase):
50 |
51 | asynchronous = True
52 |
53 | def status(self):
54 | return self.response.status
55 |
56 | async def json(self):
57 | return await self.response.json()
58 |
59 | async def text(self):
60 | return await self.response.text()
61 |
62 | async def content(self):
63 | return await self.response.read()
64 |
65 |
66 | def wrap_response(response):
67 | if isinstance(response, requests.Response):
68 | return RequestsHTTPRequestAdapter(response)
69 | elif isinstance(response, aiohttp.ClientResponse):
70 | return AIOHttpHTTPRequestAdapter(response)
71 | else:
72 | raise ValueError("No adapter found for HTTP response class %s" % response.__class__)
73 |
--------------------------------------------------------------------------------
/wolframclient/evaluation/cloud/server.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.utils import six
4 |
5 | try:
6 | import certifi
7 |
8 | DEFAULT_CA_PATH = certifi.where()
9 | except ImportError:
10 | certifi = None
11 | DEFAULT_CA_PATH = None
12 |
13 |
14 | __all__ = ["WolframServer", "DEFAULT_CA_PATH"]
15 |
16 |
17 | class WolframServer:
18 | """Represents the cloud server.
19 |
20 | Contains the authentication endpoints information, the API endpoint aka. the
21 | cloud base (`$CloudBase` in the Wolfram Language), and eventually the xauth
22 | consumer key and secret.
23 | """
24 |
25 | def __init__(
26 | self,
27 | cloudbase,
28 | request_token_endpoint,
29 | access_token_endpoint,
30 | xauth_consumer_key=None,
31 | xauth_consumer_secret=None,
32 | certificate=None,
33 | ):
34 | self.cloudbase = cloudbase
35 | self.request_token_endpoint = request_token_endpoint
36 | self.access_token_endpoint = access_token_endpoint
37 | self.xauth_consumer_key = xauth_consumer_key
38 | self.xauth_consumer_secret = xauth_consumer_secret
39 | if certificate is None or isinstance(certificate, six.string_types):
40 | self.certificate = certificate
41 | else:
42 | raise ValueError("Invalid certificate. Must be a string type or None.")
43 |
44 | def is_xauth(self):
45 | return self.xauth_consumer_key is not None and self.xauth_consumer_secret is not None
46 |
47 | def __repr__(self):
48 | return "<{}: cloudbase={}, request_token={}, access_token={}, certificate={}, xauth support={}>".format(
49 | self.__class__.__name__,
50 | self.cloudbase,
51 | self.request_token_endpoint,
52 | self.access_token_endpoint,
53 | self.certificate,
54 | self.is_xauth(),
55 | )
56 |
57 |
58 | # A built-in instance representing the Wolfram Public Cloud.
59 | WOLFRAM_PUBLIC_CLOUD_SERVER = WolframServer(
60 | "https://www.wolframcloud.com",
61 | "https://account.wolfram.com/auth/request-token",
62 | "https://account.wolfram.com/auth/access-token",
63 | xauth_consumer_key=None,
64 | xauth_consumer_secret=None,
65 | )
66 |
--------------------------------------------------------------------------------
/wolframclient/evaluation/kernel/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.evaluation.kernel.asyncsession import WolframLanguageAsyncSession
4 | from wolframclient.evaluation.kernel.localsession import WolframLanguageSession
5 |
6 | __all__ = ["WolframLanguageSession", "WolframLanguageAsyncSession"]
7 |
--------------------------------------------------------------------------------
/wolframclient/evaluation/kernel/zmqsocket.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import logging
4 | from functools import wraps
5 |
6 | from wolframclient.exception import WolframLanguageException
7 | from wolframclient.utils.api import time, zmq
8 |
9 | logger = logging.getLogger(__name__)
10 |
11 |
12 | class SocketException(WolframLanguageException):
13 | pass
14 |
15 |
16 | class SocketAborted(SocketException):
17 | pass
18 |
19 |
20 | class SocketOperationTimeout(SocketException):
21 | pass
22 |
23 |
24 | def abortable():
25 | def outer(recv_method):
26 | @wraps(recv_method)
27 | def recv_abortable(
28 | socket, timeout=None, abort_check_period=0.1, abort_event=None, **kwargs
29 | ):
30 | if not socket.bound:
31 | raise SocketException("ZMQ socket not bound.")
32 | if timeout and timeout < 0:
33 | raise ValueError("Timeout must be a positive number.")
34 | retry = 0
35 | start = time.perf_counter()
36 | # fix inconsistencies
37 | if timeout and abort_check_period > timeout:
38 | abort_check_period = timeout
39 | abort_check_period = 1000.0 * abort_check_period
40 | while True:
41 | if socket.zmq_socket.poll(timeout=abort_check_period) > 0:
42 | try:
43 | return recv_method(socket, flags=zmq.NOBLOCK, **kwargs)
44 | # just in case there is more than one consumer.
45 | except zmq.Again:
46 | pass
47 | retry += 1
48 | if abort_event and abort_event.is_set():
49 | raise SocketAborted("Socket operation aborted.")
50 | if timeout and (time.perf_counter() - start > timeout):
51 | break
52 | raise SocketOperationTimeout(
53 | "Failed to read any message from socket %s after %.1f seconds and %i retries."
54 | % (socket.uri, time.perf_counter() - start, retry)
55 | )
56 |
57 | return recv_abortable
58 |
59 | return outer
60 |
61 |
62 | class Socket:
63 | """Wrapper around ZMQ socket"""
64 |
65 | def __init__(self, protocol="tcp", host="127.0.0.1", port=None, zmq_type=zmq.PAIR):
66 | self.zmq_type = zmq_type
67 | self.uri = None
68 | self.bound = False
69 | self.zmq_socket = zmq.Context.instance().socket(zmq_type)
70 | self.closed = False
71 |
72 | def can_bind_or_fail(self):
73 | if self.bound:
74 | raise SocketException("Already bounded.")
75 | if self.closed:
76 | raise SocketException("Socket has been closed.")
77 |
78 | def bind_to_uri(self, uri):
79 | self.can_bind_or_fail()
80 | self.zmq_socket.bind(uri)
81 | self.uri = uri
82 | logger.debug("ZMQ socket bound to " + uri)
83 | self.bound = True
84 | return self.zmq_socket
85 |
86 | def bind(self, protocol="tcp", host="127.0.0.1", port=None):
87 | self.can_bind_or_fail()
88 | if protocol == "inproc":
89 | self.uri = "inproc://%s" % host
90 | self.zmq_socket.bind(self.uri)
91 | elif port:
92 | self.uri = "{}://{}:{}".format(protocol, host, port)
93 | self.zmq_socket.bind(self.uri)
94 | else:
95 | port = self.zmq_socket.bind_to_random_port("{}://{}".format(protocol, host))
96 | self.uri = "{}://{}:{}".format(protocol, host, port)
97 | logger.debug("ZMQ socket bound to " + self.uri)
98 | self.bound = True
99 | return self.zmq_socket
100 |
101 | def recv(self, *args, **kwargs):
102 | return self.zmq_socket.recv(*args, **kwargs)
103 |
104 | def recv_json(self, *args, **kwargs):
105 | return self.zmq_socket.recv_json(*args, **kwargs)
106 |
107 | def send(self, *args, **kwargs):
108 | return self.zmq_socket.send(*args, **kwargs)
109 |
110 | def poll(self, *args, **kwargs):
111 | return self.zmq_socket.poll(*args, **kwargs)
112 |
113 | @abortable()
114 | def recv_abortable(self, **kwargs):
115 | # def abortable_recv(self, timeout=None, abort_check_period=0.1, abort_event=None, copy=True):
116 | """Read a socket in a non-blocking fashion, until a timeout is reached, or until an abort Event is set."""
117 | return self.recv(**kwargs)
118 |
119 | @abortable()
120 | def recv_json_abortable(self, **kwargs):
121 | """Read a socket for a json message, in a non-blocking fashion, until a timeout is reached, or until an abort Event is set."""
122 | return self.recv_json(**kwargs)
123 |
124 | def close(self):
125 | self.zmq_socket.close()
126 | self.closed = True
127 | self.bound = False
128 |
129 | def __repr__(self):
130 | if self.bound:
131 | return "" % self.uri
132 | else:
133 | return "" % self.uri
134 |
--------------------------------------------------------------------------------
/wolframclient/exception.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.language.exceptions import WolframLanguageException
4 | from wolframclient.utils.logger import str_trim
5 |
6 |
7 | class RequestException(WolframLanguageException):
8 | """Error in an HTTP request."""
9 |
10 | def __init__(self, response, msg=None):
11 | self.response = response
12 | if msg:
13 | self.msg = msg
14 | else:
15 | try:
16 | self.msg = response.text()
17 | except UnicodeDecodeError:
18 | self.msg = "Failed to decode request body."
19 |
20 | def __str__(self):
21 | if hasattr(self.response, "status"):
22 | if callable(self.response.status):
23 | status = self.response.status()
24 | else:
25 | status = self.response.status
26 | elif hasattr(self.response, "status_code"):
27 | status = self.response.status_code
28 | else:
29 | status = "N/A"
30 | return " {}".format(status, self.msg or "")
31 |
32 |
33 | class AuthenticationException(RequestException):
34 | """Error in an authentication request."""
35 |
36 |
37 | class WolframKernelException(WolframLanguageException):
38 | """Error while interacting with a Wolfram kernel."""
39 |
40 |
41 | class WolframEvaluationException(WolframLanguageException):
42 | """Error after an evaluation raising messages."""
43 |
44 | def __init__(self, error, result=None, messages=[]):
45 | self.error = error
46 | self.result = result
47 | if isinstance(messages, list):
48 | self.messages = messages
49 | else:
50 | self.messages = [messages]
51 |
52 | def __str__(self):
53 | return self.error
54 |
55 | def __repr__(self):
56 | return "<%s error=%s, expr=%s, messages=%i>:" % (
57 | self.__class__.__name__,
58 | self.error,
59 | str_trim(self.result),
60 | len(self.messages),
61 | )
62 |
63 |
64 | class SocketException(WolframLanguageException):
65 | """Error while operating on socket."""
66 |
67 |
68 | class WolframParserException(WolframLanguageException):
69 | """Error while deserializing WXF bytes."""
70 |
71 |
72 | __all__ = [
73 | "WolframLanguageException",
74 | "RequestException",
75 | "AuthenticationException",
76 | "WolframKernelException",
77 | "SocketException",
78 | "WolframParserException",
79 | "WolframEvaluationException",
80 | ]
81 |
--------------------------------------------------------------------------------
/wolframclient/language/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.language.expression import WLInputExpression, WLSymbolFactory
4 |
5 | wl = WLSymbolFactory()
6 | """A factory of :class:`~wolframclient.language.expression.WLSymbol` instances without any particular context.
7 |
8 | This instance of :class:`~wolframclient.language.expression.WLSymbolFactory` is conveniently used
9 | by calling its attributes. The following code represents various Wolfram Language expressions::
10 |
11 | # Now
12 | wl.Now
13 | # Quantity[3, "Hours"]
14 | wl.Quantity(3, "Hours")
15 | # Select[PrimeQ, {1,2,3,4}]
16 | wl.Select(wl.PrimeQ, [1, 2, 3, 4])
17 |
18 | Represent symbols in various contexts::
19 |
20 | >>> wl.Developer.PackedArrayQ
21 | Developer`PackedArrayQ
22 |
23 | >>> wl.Global.f
24 | Global`f
25 |
26 | Specify a context and a subcontext::
27 |
28 | >>> wl.MyContext.MySubContext.SymbolName
29 | MyContext`MySubContext`SymbolName
30 |
31 |
32 | """
33 |
34 | System = wl.System
35 | """A factory of :class:`~wolframclient.language.expression.WLSymbol` instances having ``System``` context.
36 |
37 | See :class:`~wolframclient.language.expression.WLSymbolFactory` for more details.
38 |
39 | Represent a symbol in the System context::
40 |
41 | >>> System.ImageIdentify
42 | System`ImageIdentify
43 |
44 | """
45 |
46 | Global = wl.Global
47 | """A factory of :class:`~wolframclient.language.expression.WLSymbol` instances having ``Global``` context.
48 |
49 | See :class:`~wolframclient.language.expression.WLSymbolFactory` and
50 | :class:`~wolframclient.language.expression.WLSymbolFactory` for more details.
51 |
52 | Represent a symbol in the Global context::
53 |
54 | >>> Global.mySymbol
55 | Global`mySymbol
56 |
57 | Represent a function call to a function::
58 |
59 | >>> Global.myFunction('foo')
60 | Global`myFunction['foo']
61 |
62 | """
63 |
64 | # Sphinx seems to bug on this one, and picks an outdated the docstring when declared in __init__.
65 | wlexpr = WLInputExpression
66 | """ Represent Wolfram Language expressions with input form strings.
67 |
68 | Convenient alias for :class:`~wolframclient.language.expression.WLInputExpression`.
69 |
70 | Represent an expression::
71 |
72 | >>> wlexpr('Select[Range[10], EvenQ]')
73 | (Select[Range[10], EvenQ])
74 |
75 | Represent a pure function that squares an input argument::
76 |
77 | >>> wlexpr('# ^ 2 &' )
78 | (# ^ 2 &)
79 |
80 | """
81 |
82 |
83 | __all__ = ["wl", "System", "Global", "wlexpr"]
84 |
--------------------------------------------------------------------------------
/wolframclient/language/array.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import struct
4 | from functools import reduce
5 | from operator import mul
6 |
7 | from wolframclient.exception import WolframLanguageException
8 | from wolframclient.serializers.wxfencoder import constants
9 | from wolframclient.utils.encoding import force_bytes
10 |
11 | try:
12 | from collections.abc import Sequence
13 | except ImportError:
14 | from collections.abc import Sequence
15 |
16 |
17 | def pack(format, *elements):
18 | return struct.pack(b"<%i%s" % (len(elements), force_bytes(format)), *elements)
19 |
20 |
21 | class NumericArray(Sequence):
22 | def __init__(self, array, type, shape=None):
23 |
24 | self.array = array
25 | self.shape = shape or (len(array),)
26 | self.type = self._valid_type_or_fail(type)
27 | self.struct = constants.STRUCT_MAPPING[self.type]
28 |
29 | def _valid_type_or_fail(self, type):
30 | if type not in constants.STRUCT_MAPPING:
31 | raise WolframLanguageException(
32 | "Type {} is not one of the supported array types: {}.".format(
33 | type, ", ".join(constants.STRUCT_MAPPING.keys())
34 | )
35 | )
36 | return type
37 |
38 | def tobytes(self):
39 | return pack(self.struct.format[1], *self.array)
40 |
41 | def __getitem__(self, k):
42 | return self.array[k]
43 |
44 | def __len__(self):
45 | return reduce(mul, self.shape, 1)
46 |
47 |
48 | class PackedArray(NumericArray):
49 | def _valid_type_or_fail(self, type):
50 | if type not in constants.VALID_PACKED_ARRAY_LABEL_TYPES:
51 | raise WolframLanguageException(
52 | "Type {} is not one of the supported packed array types: {}.".format(
53 | type, ", ".join(sorted(constants.VALID_PACKED_ARRAY_LABEL_TYPES))
54 | )
55 | )
56 | return type
57 |
--------------------------------------------------------------------------------
/wolframclient/language/decorators.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import sys
4 | import traceback
5 | from functools import wraps
6 |
7 | from wolframclient.language import wl
8 | from wolframclient.language.exceptions import WolframLanguageException
9 | from wolframclient.serializers import DEFAULT_FORMAT, export
10 | from wolframclient.utils.encoding import force_text, safe_force_text
11 |
12 | DEFAULT_UNKNOWN_FAILURE = {
13 | "wxf": b"8:f\x02s\x07FailureS\x0dPythonFailureA\x01-S\x0fMessageTemplateS\x1aUnexpected error occurred.",
14 | "wl": b'Failure["PythonFailure", <|"MessageTemplate" -> "Unexpected error occurred."|>]',
15 | }
16 |
17 |
18 | def safe_wl_execute(function, args=(), opts={}, export_opts={}, exception_class=None):
19 |
20 | try:
21 | return export(function(*args, **opts), **export_opts)
22 | except Exception as export_exception:
23 | try:
24 | try:
25 |
26 | # The user can provide an exception class, and it can be broken, in which case we are running another
27 | # try / except to return errors that are happening during class serialization
28 |
29 | if isinstance(export_exception, WolframLanguageException):
30 | try:
31 | export_exception.set_traceback(*sys.exc_info())
32 | return export(export_exception, **export_opts)
33 | except Exception:
34 | pass
35 |
36 | if not exception_class or exception_class is WolframLanguageException:
37 | return export(
38 | WolframLanguageException(export_exception, exec_info=sys.exc_info()),
39 | **export_opts,
40 | )
41 |
42 | # A custom error class might fail, if this is happening then we can try to use the built in one
43 | return export(
44 | exception_class(export_exception, exec_info=sys.exc_info()), **export_opts
45 | )
46 | except Exception as exception_export_err:
47 | return export(
48 | WolframLanguageException(exception_export_err, exec_info=sys.exc_info()),
49 | target_format=export_opts.get("target_format", DEFAULT_FORMAT),
50 | encoder="wolframclient.serializers.encoders.builtin.encoder",
51 | )
52 |
53 | except Exception as unknown_exception:
54 |
55 | # This is the last resort.
56 | # Everything went wrong, including the code that was supposed to return a traceback, or the custom
57 | # normalizer is doing something it should not. This should never happen.
58 | try:
59 | return export(
60 | wl.Failure(
61 | "PythonFailure",
62 | {
63 | "MessageTemplate": safe_force_text(unknown_exception),
64 | "MessageParameters": {},
65 | "FailureCode": safe_force_text(
66 | unknown_exception.__class__.__name__
67 | ),
68 | "Traceback": force_text(traceback.format_exc()),
69 | },
70 | ),
71 | target_format=export_opts.get("target_format", DEFAULT_FORMAT),
72 | encoder="wolframclient.serializers.encoders.builtin.encoder",
73 | )
74 | except Exception:
75 | # Something went worst.
76 | # this might happen with import errors / syntax errors in third party pluging that are loading the
77 | # exporter and doing some real damage to the dispatcher we are using.
78 | return DEFAULT_UNKNOWN_FAILURE[
79 | export_opts.get("target_format", DEFAULT_FORMAT)
80 | ]
81 |
82 |
83 | def to_wl(exception_class=None, **export_opts):
84 | def outer(function):
85 | @wraps(function)
86 | def inner(*args, **opts):
87 | return safe_wl_execute(
88 | function=function,
89 | args=args,
90 | opts=opts,
91 | export_opts=export_opts,
92 | exception_class=exception_class,
93 | )
94 |
95 | return inner
96 |
97 | return outer
98 |
--------------------------------------------------------------------------------
/wolframclient/language/exceptions.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.language import wl
4 | from wolframclient.serializers.serializable import WLSerializable
5 | from wolframclient.utils.decorators import to_dict
6 | from wolframclient.utils.encoding import safe_force_text
7 |
8 |
9 | class WolframLanguageException(WLSerializable, Exception):
10 | """The most generic exception raised by the Wolfram Client Library.
11 |
12 | This class is :class:`~wolframclient.serializers.serializable.WLSerializable` and will automatically serialize to a
13 | failure box when evaluated in Wolfram Desktop.
14 | """
15 |
16 | def __init__(self, payload, exec_info=None):
17 |
18 | self.payload = payload
19 |
20 | if exec_info:
21 | self.set_traceback(*exec_info)
22 | else:
23 | self.set_traceback(None, None, None)
24 |
25 | def failure_tag(self):
26 | return "PythonError"
27 |
28 | def failure_template(self):
29 | return safe_force_text(self.payload)
30 |
31 | def failure_parameters(self):
32 | return {}
33 |
34 | def failure_code(self):
35 | if isinstance(self.payload, Exception):
36 | return safe_force_text(self.payload.__class__.__name__)
37 |
38 | def set_traceback(self, exc_type, exc_value, tb):
39 | self.exc_type, self.exc_value, self.tb = exc_type, exc_value, tb
40 |
41 | def to_wl(self, *args, **opts):
42 | return wl.Failure(
43 | self.failure_tag(),
44 | wl.Association(
45 | *(wl.RuleDelayed(key, value) for key, value in self.failure_meta().items())
46 | ),
47 | )
48 |
49 | def show_traceback(self):
50 | return True
51 |
52 | @to_dict
53 | def failure_meta(self):
54 |
55 | template, parameters, code = (
56 | self.failure_template(),
57 | self.failure_parameters(),
58 | self.failure_code(),
59 | )
60 |
61 | if template:
62 | yield "MessageTemplate", template
63 | yield "MessageParameters", parameters
64 |
65 | if code:
66 | yield "FailureCode", code
67 |
68 | if self.show_traceback() and self.tb:
69 |
70 | from wolframclient.language.traceback import serialize_traceback
71 |
72 | yield "Traceback", serialize_traceback(self.exc_type, self.exc_value, self.tb)
73 |
74 | def __repr__(self):
75 | return "{}: {}".format(self.__class__.__name__, self.failure_template())
76 |
--------------------------------------------------------------------------------
/wolframclient/language/expression.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from itertools import chain
4 |
5 | from wolframclient.utils import six
6 | from wolframclient.utils.encoding import force_text
7 |
8 | __all__ = ["WLSymbol", "WLFunction", "WLSymbolFactory", "WLInputExpression"]
9 |
10 |
11 | class WLExpressionMeta:
12 | """Abstract class to subclass when building representation of Wolfram Language expressions as Python object."""
13 |
14 | if six.PY2:
15 |
16 | def __nonzero__(self):
17 | return True
18 |
19 | def __bool__(self):
20 | return True
21 |
22 | def __call__(self, *args, **opts):
23 | return WLFunction(self, *args, **opts)
24 |
25 |
26 | class WLSymbol(WLExpressionMeta):
27 | """Represent a Wolfram Language symbol in Python."""
28 |
29 | __slots__ = "name"
30 |
31 | def __init__(self, name):
32 | if isinstance(name, six.binary_type):
33 | self.name = force_text(name)
34 | elif isinstance(name, six.text_type):
35 | self.name = name
36 | else:
37 | raise ValueError(
38 | "Symbol name should be {} not {}. You provided: {}".format(
39 | six.text_type.__name__, name.__class__.__name__, name
40 | )
41 | )
42 |
43 | def __hash__(self):
44 | return hash((WLSymbol.__name__, self.name))
45 |
46 | def __len__(self):
47 | return 0 # consistent with Length(x)
48 |
49 | def __eq__(self, other):
50 | return isinstance(other, WLSymbol) and self.name == other.name
51 |
52 | def __repr__(self):
53 | return self.name
54 |
55 | def __str__(self):
56 | return self.name
57 |
58 |
59 | class WLFunction(WLExpressionMeta):
60 | """Represent a Wolfram Language function with its head and arguments."""
61 |
62 | # reminder: use slots to reduce memory usage:
63 | # https://stackoverflow.com/questions/472000/usage-of-slots
64 | # https://www.codementor.io/satwikkansal/python-practices-for-efficient-code-performance-memory-and-usability-aze6oiq65
65 |
66 | __slots__ = "head", "args"
67 |
68 | def __init__(self, head, *args, **opts):
69 | self.head = head
70 |
71 | if opts:
72 | self.args = tuple(
73 | chain(args, (WLSymbol("Rule")(WLSymbol(k), v) for k, v in opts.items()))
74 | )
75 | else:
76 | self.args = args
77 |
78 | def __hash__(self):
79 | return hash((self.head, self.args))
80 |
81 | def __getitem__(self, i):
82 | return self.args.__getitem__(i)
83 |
84 | def __eq__(self, other):
85 | return (
86 | isinstance(other, WLFunction)
87 | and self.head == other.head
88 | and self.args == other.args
89 | )
90 |
91 | def __len__(self):
92 | return len(self.args)
93 |
94 | def __repr__(self):
95 | if len(self) > 4:
96 | return "%s[%s, << %i >>, %s]" % (
97 | repr(self.head),
98 | ", ".join(repr(x) for x in self.args[:2]),
99 | len(self) - 4,
100 | ", ".join(repr(x) for x in self.args[-2:]),
101 | )
102 | else:
103 | return "{}[{}]".format(repr(self.head), ", ".join(repr(x) for x in self.args))
104 |
105 |
106 | class WLSymbolFactory(WLSymbol):
107 | """Provide a convenient way to build objects representing arbitrary Wolfram Language expressions through the use of
108 | attributes.
109 |
110 | This class is conveniently instantiated at startup as :class:`~wolframclient.language.wl`,
111 | :class:`~wolframclient.language.Global`
112 | and :class:`~wolframclient.language.System`. It should be instantiated only to represent many symbols belonging to
113 | the same specific context.
114 |
115 | Example::
116 |
117 | >>> dev = WLSymbolFactory('Developer')
118 | >>> dev.PackedArrayQ
119 | Developer`PackedArrayQ
120 |
121 | Alternative::
122 |
123 | >>> wl.Developer.PackedArrayQ
124 | Developer`PackedArrayQ
125 |
126 | """
127 |
128 | def __init__(self, name=None):
129 | self.name = name
130 |
131 | def __getattr__(self, attr):
132 | # this operation is always creating a new immutable symbol factory
133 | return self.__class__(self.name and "{}`{}".format(self.name, attr) or attr)
134 |
135 |
136 | class WLInputExpression(WLExpressionMeta):
137 | """Represent a string input form expression."""
138 |
139 | def __init__(self, input):
140 | if isinstance(input, (six.binary_type, six.text_type)):
141 | self.input = input
142 | else:
143 | raise ValueError("input must be string or bytes")
144 |
145 | def __repr__(self):
146 | return "(%s)" % self.input
147 |
148 | def __str__(self):
149 | return "(%s)" % self.input
150 |
--------------------------------------------------------------------------------
/wolframclient/language/side_effects.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import logging
4 |
5 | from wolframclient.language import wl
6 | from wolframclient.language.expression import WLExpressionMeta
7 | from wolframclient.utils.functional import riffle
8 |
9 | side_effect_logger = logging.getLogger("wolframclient.side_effect")
10 |
11 | # the side effect logger is used by ExternalEvaluate to send side effects to the kernel.
12 |
13 |
14 | def wl_print(*payload):
15 | return wl_side_effect(wl.Print(wl.Row(riffle(payload, " "))))
16 |
17 |
18 | def wl_side_effect(payload):
19 | if not isinstance(payload, WLExpressionMeta):
20 | raise ValueError("Only expressions can create side_effects in wl")
21 |
22 | side_effect_logger.warning(payload)
23 |
--------------------------------------------------------------------------------
/wolframclient/serializers/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.serializers.encoder import wolfram_encoder
4 | from wolframclient.serializers.serializable import WLSerializable
5 | from wolframclient.utils.importutils import API
6 |
7 | __all__ = ["export", "WLSerializable", "wolfram_encoder"]
8 |
9 | DEFAULT_FORMAT = "wl"
10 |
11 | available_formats = API(
12 | wl="wolframclient.serializers.wl.WLSerializer",
13 | wxf="wolframclient.serializers.wxf.WXFSerializer",
14 | )
15 |
16 |
17 | def export(data, stream=None, target_format=DEFAULT_FORMAT, **options):
18 | """Serialize input `data` to a target format.
19 |
20 | Input `data` can be any supported Python type, including :class:`list`, :class:`dict` or any serializable Python
21 | object.
22 |
23 | Serializable python objects are class extending :class:`~wolframclient.serializers.serializable.WLSerializable` and
24 | types declared in an encoder.
25 |
26 | The default format is :wl:`InputForm` string::
27 |
28 | >>> export(wl.Range(3))
29 | b'Range[3]'
30 |
31 | Specify WXF format by setting `target_format`::
32 |
33 | >>> export([1,2,3], target_format='wxf')
34 | b'8:f\x03s\x04ListC\x01C\x02C\x03'
35 |
36 | .. note :: WXF is a binary format for serializing Wolfram Language expression. Consult the
37 | `format specifications `_ for in
38 | depth format description.
39 |
40 | WXF byte arrays are deserialized with :func:`~wolframclient.deserializers.binary_deserialize`::
41 |
42 | >>> wxf = export([1,2,3], target_format='wxf')
43 | >>> binary_deserialize(wxf)
44 | [1, 2, 3]
45 |
46 | If `stream` is specified with a string, it is interpreted as a file path and the serialized form is written directly
47 | to the specified file. The file is opened and closed automatically::
48 |
49 | >>> export([1, 2, 3], stream='file.wl')
50 | 'file.wl'
51 |
52 | If `stream` is specified with an output stream, the serialization bytes are written to it.
53 |
54 | Any object that implements a `write` method, e.g. :data:`file`, :class:`io.BytesIO` or :class:`io.StringIO`, is a
55 | valid value for the `stream` named parameter::
56 |
57 | >>> with open('file.wl', 'wb') as f:
58 | ... export([1, 2, 3], stream=f)
59 | ...
60 |
61 |
62 | """
63 | if target_format not in available_formats:
64 | raise ValueError(
65 | "Invalid export format {}. Choices are: {}".format(
66 | target_format, ", ".join(available_formats.keys())
67 | )
68 | )
69 | return available_formats[target_format](**options).export(data, stream=stream)
70 |
--------------------------------------------------------------------------------
/wolframclient/serializers/base.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import datetime
4 | import re
5 | from itertools import chain
6 |
7 | from wolframclient.serializers.encoder import Encoder
8 | from wolframclient.serializers.wxfencoder.constants import WXF_HEADER_SEPARATOR, WXF_VERSION
9 | from wolframclient.serializers.wxfencoder.utils import (
10 | numeric_array_to_wxf,
11 | packed_array_to_wxf,
12 | )
13 | from wolframclient.utils import six
14 | from wolframclient.utils.api import timezone
15 | from wolframclient.utils.encoding import concatenate_bytes
16 |
17 |
18 | def _is_zoneinfo(tzinfo):
19 | try:
20 | return isinstance(tzinfo, timezone.ZoneInfo)
21 | except ImportError:
22 | return False
23 |
24 |
25 | class FormatSerializer(Encoder):
26 | def generate_bytes(self, data):
27 | raise NotImplementedError
28 |
29 | def export(self, data, stream=None):
30 | if stream:
31 | if isinstance(stream, six.string_types):
32 | with open(stream, "wb") as file:
33 | for token in self.generate_bytes(data):
34 | file.write(token)
35 | return stream
36 |
37 | for token in self.generate_bytes(data):
38 | stream.write(token)
39 | return stream
40 |
41 | return concatenate_bytes(self.generate_bytes(data))
42 |
43 | # implementation of several methods
44 |
45 | def serialize_function(self, head, args, **opts):
46 | raise NotImplementedError
47 |
48 | def serialize_symbol(self, symbol):
49 | raise NotImplementedError
50 |
51 | def serialize_string(self, obj):
52 | raise NotImplementedError
53 |
54 | def serialize_float(self, obj):
55 | raise NotImplementedError
56 |
57 | def serialize_decimal(self, obj):
58 | raise NotImplementedError
59 |
60 | def serialize_int(self, obj):
61 | raise NotImplementedError
62 |
63 | def serialize_bytes(self, bytes, as_byte_array=not six.PY2):
64 | raise NotImplementedError
65 |
66 | def serialize_input_form(self, string):
67 | return self.serialize_function(
68 | self.serialize_symbol(b"ToExpression"), (self.serialize_string(string),)
69 | )
70 |
71 | def _serialize_as_wxf(self, data, shape, wl_type, constructor):
72 | payload = concatenate_bytes(
73 | chain((WXF_VERSION, WXF_HEADER_SEPARATOR), constructor(data, shape, wl_type))
74 | )
75 |
76 | return self.serialize_function(
77 | self.serialize_symbol(b"BinaryDeserialize"),
78 | (self.serialize_bytes(payload, as_byte_array=True),),
79 | )
80 |
81 | def serialize_numeric_array(self, data, shape, wl_type):
82 | return self._serialize_as_wxf(data, shape, wl_type, numeric_array_to_wxf)
83 |
84 | def serialize_packed_array(self, data, shape, wl_type):
85 | return self._serialize_as_wxf(data, shape, wl_type, packed_array_to_wxf)
86 |
87 | def serialize_iterable(self, iterable, **opts):
88 | return self.serialize_function(self.serialize_symbol(b"List"), iterable, **opts)
89 |
90 | def serialize_mapping(self, mappable, **opts):
91 | return self.serialize_function(
92 | self.serialize_symbol(b"Association"),
93 | (self.serialize_rule(key, value) for key, value in mappable),
94 | **opts,
95 | )
96 |
97 | def serialize_association(self, mappable, **opts):
98 | return self.serialize_function(
99 | self.serialize_symbol(b"Association"),
100 | (self.serialize_rule(key, value) for key, value in mappable),
101 | **opts,
102 | )
103 |
104 | def serialize_fraction(self, o):
105 | return self.serialize_function(
106 | self.serialize_symbol(b"Rational"),
107 | (self.serialize_int(o.numerator), self.serialize_int(o.denominator)),
108 | )
109 |
110 | def serialize_complex(self, o):
111 | return self.serialize_function(
112 | self.serialize_symbol(b"Complex"),
113 | (self.serialize_float(o.real), self.serialize_float(o.imag)),
114 | )
115 |
116 | def serialize_rule(self, lhs, rhs):
117 | return self.serialize_function(self.serialize_symbol(b"Rule"), (lhs, rhs))
118 |
119 | def serialize_rule_delayed(self, lhs, rhs):
120 | return self.serialize_function(self.serialize_symbol(b"RuleDelayed"), (lhs, rhs))
121 |
122 | def serialize_tzinfo(
123 | self, tzinfo, date=None, name_match=re.compile("^([A-Za-z]+/[A-Za-z]+?|UTC)$")
124 | ):
125 |
126 | if tzinfo is None:
127 | return self.serialize_symbol(
128 | self.target_kernel_version >= 12 and b"None" or b"$TimeZone"
129 | )
130 |
131 | if name_match:
132 |
133 | for name in (tzinfo.tzname(None), _is_zoneinfo(tzinfo) and tzinfo.key or None):
134 |
135 | if name and name_match.match(name):
136 | return self.serialize_string(name)
137 |
138 | return self.serialize_float(
139 | tzinfo.utcoffset(date or datetime.datetime.utcnow()).total_seconds() / 3600
140 | )
141 |
--------------------------------------------------------------------------------
/wolframclient/serializers/encoders/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
--------------------------------------------------------------------------------
/wolframclient/serializers/encoders/astropy.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import sys
4 | from astropy.units import Quantity
5 |
6 | from wolframclient.utils.dispatch import Dispatch
7 |
8 | encoder = Dispatch()
9 |
10 | @encoder.dispatch(Quantity)
11 | def encode_quantity(serializer, o):
12 |
13 | # this is a patch for Quantity that for some reason is a subclass of a numpy array withtout really being a numpy array.
14 | # this implementation should also be revisited because ideally it should just be able to forward the object the fallback implementation without repeating it.
15 |
16 | if serializer.object_processor:
17 | return serializer.object_processor(serializer, o)
18 |
19 | raise NotImplementedError("Cannot serialize object of class %s" % o.__class__)
20 |
--------------------------------------------------------------------------------
/wolframclient/serializers/encoders/datetime.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import datetime
4 |
5 | from wolframclient.utils.dispatch import Dispatch
6 |
7 | encoder = Dispatch()
8 |
9 |
10 | @encoder.dispatch(datetime.datetime)
11 | def encode_datetime(serializer, o):
12 | return serializer.serialize_function(
13 | serializer.serialize_symbol(b"DateObject"),
14 | (
15 | serializer.serialize_iterable(
16 | (
17 | serializer.serialize_int(o.year),
18 | serializer.serialize_int(o.month),
19 | serializer.serialize_int(o.day),
20 | serializer.serialize_int(o.hour),
21 | serializer.serialize_int(o.minute),
22 | serializer.serialize_float(o.second + o.microsecond / 1000000.0),
23 | )
24 | ),
25 | serializer.serialize_string("Instant"),
26 | serializer.serialize_string("Gregorian"),
27 | serializer.serialize_tzinfo(o.tzinfo, o),
28 | ),
29 | )
30 |
31 |
32 | @encoder.dispatch(datetime.tzinfo)
33 | def encode_tzinfo(serializer, o):
34 | return serializer.serialize_tzinfo(o)
35 |
36 |
37 | @encoder.dispatch(datetime.timedelta)
38 | def encode_timedelta(serializer, o):
39 | return serializer.serialize_function(
40 | serializer.serialize_symbol(b"Quantity"),
41 | (
42 | serializer.serialize_float(o.total_seconds()),
43 | serializer.serialize_string("Seconds"),
44 | ),
45 | )
46 |
47 |
48 | @encoder.dispatch(datetime.date)
49 | def encode_date(serializer, o):
50 | return serializer.serialize_function(
51 | serializer.serialize_symbol(b"DateObject"),
52 | (
53 | serializer.serialize_iterable(
54 | (
55 | serializer.serialize_int(o.year),
56 | serializer.serialize_int(o.month),
57 | serializer.serialize_int(o.day),
58 | )
59 | ),
60 | serializer.serialize_string("Day"),
61 | serializer.serialize_string("Gregorian"),
62 | serializer.serialize_symbol(b"None"),
63 | ),
64 | )
65 |
66 |
67 | @encoder.dispatch(datetime.time)
68 | def encode_time(serializer, o):
69 |
70 | inner = [
71 | serializer.serialize_iterable(
72 | (
73 | serializer.serialize_int(o.hour),
74 | serializer.serialize_int(o.minute),
75 | serializer.serialize_float(o.second + o.microsecond / 1000000.0),
76 | )
77 | )
78 | ]
79 |
80 | if o.tzinfo:
81 | inner.append(
82 | serializer.serialize_rule(
83 | serializer.serialize_symbol(b"TimeZone"),
84 | serializer.serialize_tzinfo(
85 | o.tzinfo,
86 | datetime.datetime.combine(datetime.date.today(), o),
87 | name_match=None,
88 | ),
89 | )
90 | )
91 |
92 | return serializer.serialize_function(serializer.serialize_symbol(b"TimeObject"), inner)
93 |
--------------------------------------------------------------------------------
/wolframclient/serializers/encoders/decimal.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import decimal
4 |
5 | from wolframclient.utils.dispatch import Dispatch
6 |
7 | encoder = Dispatch()
8 |
9 |
10 | @encoder.dispatch(decimal.Decimal)
11 | def encode_decimal(serializer, o):
12 |
13 | if o.is_infinite():
14 | return serializer.serialize_function(
15 | serializer.serialize_symbol(b"DirectedInfinity"),
16 | (serializer.serialize_int(o < 0 and -1 or 1),),
17 | )
18 |
19 | if o.is_nan():
20 | return serializer.serialize_symbol(b"Indeterminate")
21 |
22 | return serializer.serialize_decimal(o)
23 |
--------------------------------------------------------------------------------
/wolframclient/serializers/encoders/fractions.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import fractions
4 |
5 | from wolframclient.utils.dispatch import Dispatch
6 |
7 | encoder = Dispatch()
8 |
9 |
10 | @encoder.dispatch(fractions.Fraction)
11 | def encode_faction(serializer, o):
12 | return serializer.serialize_fraction(o)
13 |
--------------------------------------------------------------------------------
/wolframclient/serializers/encoders/io.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import io
4 |
5 | from wolframclient.utils.dispatch import Dispatch
6 |
7 | encoder = Dispatch()
8 |
9 | @encoder.dispatch(io.IOBase.__mro__)
10 | def encode_quantity(serializer, o):
11 |
12 | # we need to patch io objects because we automatically attempt to convert classes that are iterable to a list, however it should not be done in this case. To be improved.
13 |
14 | if serializer.object_processor:
15 | return serializer.object_processor(serializer, o)
16 |
17 | raise NotImplementedError("Cannot serialize object of class %s" % o.__class__)
18 |
--------------------------------------------------------------------------------
/wolframclient/serializers/encoders/numpy.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import sys
4 |
5 | from wolframclient.utils.api import numpy
6 | from wolframclient.utils.dispatch import Dispatch
7 | from wolframclient.utils.functional import map
8 |
9 | encoder = Dispatch()
10 |
11 |
12 | NUMPY_MAPPING = {
13 | numpy.int8: ("Integer8", None),
14 | numpy.int16: ("Integer16", None),
15 | numpy.int32: ("Integer32", None),
16 | numpy.intc: ("Integer32", None),
17 | numpy.int64: ("Integer64", None),
18 | numpy.uint8: ("UnsignedInteger8", None),
19 | numpy.uint16: ("UnsignedInteger16", None),
20 | numpy.uint32: ("UnsignedInteger32", None),
21 | numpy.uintc: ("UnsignedInteger32", None),
22 | numpy.uint64: ("UnsignedInteger64", None),
23 | numpy.float32: ("Real32", None),
24 | numpy.float64: ("Real64", None),
25 | numpy.complex64: ("ComplexReal32", None),
26 | numpy.complex128: ("ComplexReal64", None),
27 | }
28 |
29 |
30 | PACKED_NUMPY_MAPPING = {
31 | numpy.int8: ("Integer8", None),
32 | numpy.int16: ("Integer16", None),
33 | numpy.int32: ("Integer32", None),
34 | numpy.intc: ("Integer32", None),
35 | numpy.int64: ("Integer64", None),
36 | numpy.uint8: ("Integer16", numpy.int16),
37 | numpy.uint16: ("Integer32", numpy.int32),
38 | numpy.uint32: ("Integer64", numpy.int64),
39 | numpy.uintc: ("Integer64", numpy.int64),
40 | # numpy.uint64: ("UnsignedInteger64", None),
41 | numpy.float32: ("Real32", None),
42 | numpy.float64: ("Real64", None),
43 | numpy.complex64: ("ComplexReal32", None),
44 | numpy.complex128: ("ComplexReal64", None),
45 | }
46 |
47 | SYS_IS_LE = sys.byteorder == "little"
48 |
49 |
50 | def to_little_endian(array, inplace=False):
51 | """Return a numpy array of the same type with little endian byte ordering.
52 |
53 | Set `inplace` to `True` to mutate the input array."""
54 | endianness = array.dtype.byteorder
55 | if endianness == ">" or (endianness == "=" and not SYS_IS_LE):
56 | return array.byteswap(inplace=inplace).newbyteorder()
57 | else:
58 | return array
59 |
60 |
61 | def _iencode(serializer, o, mapping, processor):
62 |
63 | if not o.shape:
64 | return serializer.encode(o.item())
65 |
66 | try:
67 | wl_type, cast_to = mapping[o.dtype.type]
68 | except KeyError:
69 | raise NotImplementedError(
70 | "Numpy serialization not implemented for {}. Choices are: {}".format(
71 | repr(o.dtype), ", ".join(map(repr, mapping.keys()))
72 | )
73 | )
74 | o = to_little_endian(o)
75 |
76 | if cast_to is not None:
77 | o = o.astype(cast_to)
78 |
79 | if hasattr(o, "tobytes"):
80 | # Numpy 1.9+ support array.tobytes, but previous versions don't and use tostring instead.
81 | data = o.tobytes()
82 | else:
83 | data = o.tostring()
84 |
85 | return processor(data, o.shape, wl_type)
86 |
87 |
88 | @encoder.dispatch(numpy.PackedArray)
89 | def encode_ndarray(serializer, o):
90 | return _iencode(serializer, o, PACKED_NUMPY_MAPPING, serializer.serialize_packed_array)
91 |
92 |
93 | @encoder.dispatch(numpy.ndarray)
94 | def encode_ndarray(serializer, o):
95 | return _iencode(serializer, o, NUMPY_MAPPING, serializer.serialize_numeric_array)
96 |
97 |
98 | @encoder.dispatch(numpy.integer)
99 | def encode_numpy_int(serializer, o):
100 | return serializer.serialize_int(int(o))
101 |
102 |
103 | @encoder.dispatch(numpy.floating)
104 | def encode_numpy_floating(serializer, o):
105 | # mantissa, and base 2 exponent.
106 | mantissa, exp = numpy.frexp(o)
107 | return serializer.serialize_function(
108 | serializer.serialize_symbol(b"Times"),
109 | (
110 | serializer.serialize_float(mantissa),
111 | serializer.serialize_function(
112 | serializer.serialize_symbol(b"Power"),
113 | (serializer.serialize_int(2), serializer.serialize_float(exp)),
114 | ),
115 | ),
116 | )
117 |
118 |
119 | @encoder.dispatch((numpy.float16, numpy.float32, numpy.float64))
120 | def encode_numpy_mp_float(serializer, o):
121 | return serializer.serialize_float(o)
122 |
123 |
124 | @encoder.dispatch(numpy.complexfloating)
125 | def encode_complex(serializer, o):
126 | return serializer.serialize_complex(o)
127 |
--------------------------------------------------------------------------------
/wolframclient/serializers/encoders/pil.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import sys
4 |
5 | from wolframclient.language import wl
6 | from wolframclient.utils import six
7 | from wolframclient.utils.api import PIL, numpy
8 | from wolframclient.utils.dispatch import Dispatch
9 |
10 | encoder = Dispatch()
11 | """ Serialize a given :class:`PIL.Image` into a Wolfram Language image.
12 |
13 | This method first tries to extract the data and relevant information about the image,
14 | and reconstruct it using :wl:`Image` constructor. This is the most efficient way to
15 | proceed, but is not guaranteed to work all the time. Only some pixel representations
16 | are supported, fortunatelly the most common ones.
17 |
18 | When the internal PIL representation does not correspond to one of the Wolfram Language,
19 | the image is converted to its format, if specified, or ultimately to PNG. This may fail,
20 | in which case an exception is raised and there is nothing more we can do.
21 |
22 | In theory we could represent any image, but due to :func:`~PIL.Image.convert()` behavior
23 | we can't. This function is not converting, but naively casts to a given type without rescaling.
24 | e.g. int32 values above 255 converted to bytes become 255. We can't cast properly from 'I' mode
25 | since some format are using 'I' for say 'I;16' (int16) and the rawmode is not always accessible.
26 |
27 | See bug in convert: https://github.com/python-pillow/Pillow/issues/3159
28 | """
29 |
30 | MODE_MAPPING = {
31 | # mode : (type, colorspace, interleaving)
32 | "1": ("Bit", None, True),
33 | "L": ("Byte", "Grayscale", True),
34 | "RGB": ("Byte", "RGB", True),
35 | "CMYK": ("Byte", "CMYK", True),
36 | "LAB": ("Byte", "LAB", True),
37 | "F": ("Real32", None, True),
38 | "RGBA": ("Byte", "RGB", True),
39 | "HSV": ("Byte", "HSB", True),
40 | }
41 | SYS_IS_LE = sys.byteorder == "little"
42 |
43 |
44 | def normalize_array(array):
45 | if array.dtype == numpy.dtype("bool"):
46 | array = array.astype(">> export(MyPythonClass('foo', 'bar'))
25 | b'MyWolframFunction["foo", "bar"]'
26 |
27 | Serialization is applied recursively; arguments are also serialized::
28 |
29 | >>> export(MyPythonClass(1, 2, MyPythonClass(2, 3)))
30 | 'MyWolframFunction[1, 2, MyWolframFunction[2, 3]]'
31 |
32 | """
33 |
34 | def to_wl(self):
35 | """Return the serialized form of a given Python class.
36 |
37 | The returned value must be a combination of serializable types.
38 | """
39 | raise NotImplementedError(
40 | "class %s must implement a to_wl method" % self.__class__.__name__
41 | )
42 |
--------------------------------------------------------------------------------
/wolframclient/serializers/utils.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import decimal
4 | import re
5 |
6 | from wolframclient.utils.encoding import force_bytes
7 |
8 | # replacement method borrowed from json
9 |
10 |
11 | ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
12 | ESCAPE_DCT = {
13 | "\\": "\\\\",
14 | '"': '\\"',
15 | "\b": "\\b",
16 | "\f": "\\f",
17 | "\n": "\\n",
18 | "\r": "\\r",
19 | "\t": "\\t",
20 | }
21 | for i in range(0x20):
22 | ESCAPE_DCT.setdefault(chr(i), "\\u{:04x}".format(i))
23 | # ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
24 | del i
25 |
26 |
27 | def replace(match):
28 | return ESCAPE_DCT[match.group(0)]
29 |
30 |
31 | def py_encode_text(s):
32 | yield b'"'
33 | yield force_bytes(ESCAPE.sub(replace, s), encoding="iso-8859-1")
34 | yield b'"'
35 |
36 |
37 | def py_encode_decimal(number, prec=decimal.getcontext().prec):
38 | return "{:f}``{:d}".format(number, prec).encode("utf-8")
39 |
40 |
41 | def safe_len(obj):
42 | try:
43 | return len(obj)
44 | except TypeError:
45 | return
46 |
--------------------------------------------------------------------------------
/wolframclient/serializers/wl.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from itertools import chain, starmap
4 |
5 | from wolframclient.serializers.base import FormatSerializer
6 | from wolframclient.serializers.utils import py_encode_decimal, py_encode_text
7 | from wolframclient.utils import six
8 | from wolframclient.utils.api import base64
9 | from wolframclient.utils.encoding import force_bytes, force_text
10 |
11 |
12 | def yield_with_separators(iterable, first, last, separator=b", "):
13 | yield first
14 | for i, arg in enumerate(iterable):
15 | if i:
16 | yield separator
17 | yield from arg
18 | yield last
19 |
20 |
21 | class WLSerializer(FormatSerializer):
22 | def __init__(self, normalizer=None, indent=None, **opts):
23 | super().__init__(normalizer=normalizer, **opts)
24 | self.indent = indent
25 |
26 | def generate_bytes(self, data):
27 | return self.encode(data)
28 |
29 | def serialize_function(self, head, args, **opts):
30 | return chain(head, yield_with_separators(args, first=b"[", last=b"]"))
31 |
32 | def serialize_symbol(self, name):
33 | yield force_bytes(name)
34 |
35 | def serialize_string(self, string):
36 | return py_encode_text(string)
37 |
38 | def serialize_bytes(self, bytes, as_byte_array=not six.PY2):
39 |
40 | # by default we are serializing as_byte_array for PY3,
41 | # py2 is by default using strings
42 |
43 | if as_byte_array:
44 | return self.serialize_function(
45 | self.serialize_symbol(b"ByteArray"), ((b'"', base64.b64encode(bytes), b'"'),)
46 | )
47 | else:
48 | return self.serialize_string(force_text(bytes, "iso-8859-1"))
49 |
50 | def serialize_decimal(self, number):
51 | yield py_encode_decimal(number)
52 |
53 | def serialize_float(self, number):
54 | yield (b"%.13f" % number).rstrip(b"0")
55 |
56 | def serialize_int(self, number):
57 | yield b"%i" % number
58 |
59 | def serialize_rule(self, lhs, rhs):
60 | return chain(lhs, (b" -> ",), rhs)
61 |
62 | def serialize_rule_delayed(self, lhs, rhs):
63 | return chain(lhs, (b" :> ",), rhs)
64 |
65 | def serialize_mapping(self, mapping, **opts):
66 | return yield_with_separators(
67 | starmap(self.serialize_rule, mapping), first=b"<|", last=b"|>"
68 | )
69 |
70 | def serialize_association(self, mapping, **opts):
71 | return self.serialize_mapping(mapping, **opts)
72 |
73 | def serialize_iterable(self, iterable, **opts):
74 | return yield_with_separators(iterable, first=b"{", last=b"}")
75 |
76 | def serialize_input_form(self, string):
77 | yield b"("
78 | yield force_bytes(string)
79 | yield b")"
80 |
--------------------------------------------------------------------------------
/wolframclient/serializers/wxf.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from itertools import chain, starmap
4 |
5 | from wolframclient.serializers.base import FormatSerializer
6 | from wolframclient.serializers.utils import py_encode_decimal, safe_len
7 | from wolframclient.serializers.wxfencoder.constants import (
8 | WXF_CONSTANTS,
9 | WXF_HEADER_COMPRESS,
10 | WXF_HEADER_SEPARATOR,
11 | WXF_VERSION,
12 | )
13 | from wolframclient.serializers.wxfencoder.utils import (
14 | float_to_bytes,
15 | integer_size,
16 | integer_to_bytes,
17 | numeric_array_to_wxf,
18 | packed_array_to_wxf,
19 | varint_bytes,
20 | )
21 | from wolframclient.utils import six
22 | from wolframclient.utils.api import zlib
23 | from wolframclient.utils.encoding import concatenate_bytes, force_bytes, force_text
24 | from wolframclient.utils.functional import partition
25 |
26 |
27 | def serialize_rule(key, value, sep=(WXF_CONSTANTS.Rule,)):
28 | return chain(sep, key, value)
29 |
30 |
31 | def get_length(iterable, length=None):
32 | if length is not None:
33 | return iterable, length
34 |
35 | length = safe_len(iterable)
36 |
37 | if length is not None:
38 | return iterable, length
39 |
40 | iterable = tuple(iterable)
41 |
42 | return iterable, len(iterable)
43 |
44 |
45 | def compress(data):
46 |
47 | compressor = zlib.compressobj()
48 |
49 | yield from map(compressor.compress, map(concatenate_bytes, partition(data, 100)))
50 |
51 | yield compressor.flush()
52 |
53 |
54 | class WXFSerializer(FormatSerializer):
55 | """Serialize python objects to WXF."""
56 |
57 | def __init__(self, normalizer=None, compress=False, **opts):
58 | super().__init__(normalizer=normalizer, **opts)
59 | self.compress = compress
60 |
61 | def generate_bytes(self, data):
62 |
63 | if self.compress:
64 |
65 | return chain(
66 | (WXF_VERSION, WXF_HEADER_COMPRESS, WXF_HEADER_SEPARATOR),
67 | compress(self.encode(data)),
68 | )
69 |
70 | return chain((WXF_VERSION, WXF_HEADER_SEPARATOR), self.encode(data))
71 |
72 | def serialize_symbol(self, name):
73 | yield WXF_CONSTANTS.Symbol
74 | yield varint_bytes(len(name))
75 | yield force_bytes(name)
76 |
77 | def serialize_function(self, head, args, **opts):
78 |
79 | iterable, length = get_length(args, **opts)
80 |
81 | return chain(
82 | (WXF_CONSTANTS.Function, varint_bytes(length)), head, chain.from_iterable(iterable)
83 | )
84 |
85 | # numeric
86 | def serialize_int(self, number):
87 | try:
88 | wxf_type, int_size = integer_size(number)
89 | yield wxf_type
90 | yield integer_to_bytes(number, int_size)
91 |
92 | except ValueError:
93 | # WXFExprInteger is raising a ValueError if the integer is not in the appropriate bounds.
94 | # that check needs to be done in case, it's better to do it only once.
95 |
96 | number = b"%i" % number
97 |
98 | yield WXF_CONSTANTS.BigInteger
99 | yield varint_bytes(len(number))
100 | yield number
101 |
102 | def serialize_float(self, number):
103 | yield WXF_CONSTANTS.Real64
104 | yield float_to_bytes(number)
105 |
106 | def serialize_decimal(self, number):
107 | number = py_encode_decimal(number)
108 | yield WXF_CONSTANTS.BigReal
109 | yield varint_bytes(len(number))
110 | yield number
111 |
112 | # text / bytes
113 |
114 | def serialize_string(self, string):
115 | string = force_bytes(string)
116 | yield WXF_CONSTANTS.String
117 | yield varint_bytes(len(string))
118 | yield string
119 |
120 | def serialize_bytes(self, bytes, as_byte_array=not six.PY2):
121 | if as_byte_array:
122 | return (WXF_CONSTANTS.BinaryString, varint_bytes(len(bytes)), bytes)
123 | else:
124 | return self.serialize_string(force_text(bytes, encoding="iso8859-1"))
125 |
126 | def serialize_mapping(self, keyvalue, **opts):
127 | # the normalizer is always sending an generator key, value
128 |
129 | iterable, length = get_length(keyvalue, **opts)
130 |
131 | return chain(
132 | (WXF_CONSTANTS.Association, varint_bytes(length)),
133 | chain.from_iterable(starmap(serialize_rule, iterable)),
134 | )
135 |
136 | def serialize_numeric_array(self, data, dimensions, wl_type):
137 | return numeric_array_to_wxf(data, dimensions, wl_type)
138 |
139 | def serialize_packed_array(self, data, dimensions, wl_type):
140 | return packed_array_to_wxf(data, dimensions, wl_type)
141 |
--------------------------------------------------------------------------------
/wolframclient/serializers/wxfencoder/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
--------------------------------------------------------------------------------
/wolframclient/serializers/wxfencoder/constants.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import struct
4 |
5 | from wolframclient.utils import six
6 | from wolframclient.utils.datastructures import Settings
7 |
8 | if six.JYTHON:
9 | pass
10 |
11 | if six.PY2:
12 |
13 | def _bytes(value):
14 | return chr(value)
15 |
16 | else:
17 |
18 | def _bytes(value):
19 | return bytes((value,))
20 |
21 |
22 | WXF_VERSION = b"8"
23 | WXF_HEADER_SEPARATOR = b":"
24 | WXF_HEADER_COMPRESS = b"C"
25 |
26 | # The list of all the WXF tokens.
27 | WXF_CONSTANTS = Settings(
28 | Function=b"f",
29 | Symbol=b"s",
30 | String=b"S",
31 | BinaryString=b"B",
32 | Integer8=b"C",
33 | Integer16=b"j",
34 | Integer32=b"i",
35 | Integer64=b"L",
36 | Real64=b"r",
37 | BigInteger=b"I",
38 | BigReal=b"R",
39 | PackedArray=_bytes(0xC1),
40 | NumericArray=_bytes(0xC2),
41 | Association=b"A",
42 | Rule=b"-",
43 | RuleDelayed=b":",
44 | )
45 |
46 | # The list of all array value type tokens.
47 | ARRAY_TYPES = Settings(
48 | Integer8=_bytes(0x00),
49 | Integer16=_bytes(0x01),
50 | Integer32=_bytes(0x02),
51 | Integer64=_bytes(0x03),
52 | UnsignedInteger8=_bytes(0x10),
53 | UnsignedInteger16=_bytes(0x11),
54 | UnsignedInteger32=_bytes(0x12),
55 | UnsignedInteger64=_bytes(0x13),
56 | Real32=_bytes(0x22),
57 | Real64=_bytes(0x23),
58 | ComplexReal32=_bytes(0x33),
59 | ComplexReal64=_bytes(0x34),
60 | )
61 | ARRAY_TYPES_FROM_WXF_TYPES = {v: k for k, v in ARRAY_TYPES.items()}
62 |
63 |
64 | ARRAY_TYPES_ELEM_SIZE = {
65 | ARRAY_TYPES.Integer8: 1,
66 | ARRAY_TYPES.Integer16: 2,
67 | ARRAY_TYPES.Integer32: 4,
68 | ARRAY_TYPES.Integer64: 8,
69 | ARRAY_TYPES.UnsignedInteger8: 1,
70 | ARRAY_TYPES.UnsignedInteger16: 2,
71 | ARRAY_TYPES.UnsignedInteger32: 4,
72 | ARRAY_TYPES.UnsignedInteger64: 8,
73 | ARRAY_TYPES.Real32: 4,
74 | ARRAY_TYPES.Real64: 8,
75 | ARRAY_TYPES.ComplexReal32: 8,
76 | ARRAY_TYPES.ComplexReal64: 16,
77 | }
78 | """ A set of all valid value type tokens for PackedArray.
79 | There is no restriction for NumericArray value types. """
80 | VALID_PACKED_ARRAY_TYPES = frozenset(
81 | (
82 | ARRAY_TYPES.Integer8,
83 | ARRAY_TYPES.Integer16,
84 | ARRAY_TYPES.Integer32,
85 | ARRAY_TYPES.Integer64,
86 | ARRAY_TYPES.Real32,
87 | ARRAY_TYPES.Real64,
88 | ARRAY_TYPES.ComplexReal32,
89 | ARRAY_TYPES.ComplexReal64,
90 | )
91 | )
92 |
93 | VALID_PACKED_ARRAY_LABEL_TYPES = frozenset(
94 | (
95 | "Integer8",
96 | "Integer16",
97 | "Integer32",
98 | "Integer64",
99 | "Real32",
100 | "Real64",
101 | "ComplexReal32",
102 | "ComplexReal64",
103 | )
104 | )
105 |
106 | STRUCT_MAPPING = Settings(
107 | Integer8=struct.Struct(b" 0:
104 | chunk = self._compressor.decompress(data_in, size - out_len)
105 | else:
106 | chunk = self._compressor.decompress(data_in)
107 | # increment output len.
108 | out_len += len(chunk)
109 | # write to buffer
110 | yield chunk
111 | # check requested size against output length.
112 | if size > 0 and out_len == size:
113 | break
114 |
--------------------------------------------------------------------------------
/wolframclient/serializers/wxfencoder/wxfexprprovider.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.serializers.wxfencoder.wxfencoder import (
4 | DefaultWXFEncoder,
5 | NotEncodedException,
6 | WXFEncoder,
7 | )
8 |
9 |
10 | class WXFExprProvider:
11 | """
12 | Expression provider pull instances of WXFExpr from instances of `WXFEncoder`.
13 |
14 | `WXFExprProvider` can be initialized with an encoder. If none is provided the
15 | default class `DefaultWXFEncoder` is used to instantiate one. It is possible
16 | to add extra encoder using `add_encoder`. The order in which encoders are called
17 | is the one in which they were added. Note that for performance reasons, it is
18 | recommended to have the default encoder be first, as such one should not initialize
19 | a provider with an encoder except if the default one is not suited for ones needs.
20 |
21 | An optional `default` function can be passed to the provider at initialization. It
22 | is used in last resort if no encoder handled the object. It is applied to the
23 | object and is expected to transform it to something serializable
24 | e.g: a string using `default=repr`.
25 |
26 | One must carefully design the `default` function to avoid stack overflow.
27 | """
28 |
29 | def __init__(self, encoder=None, default=None):
30 | self.encoders = []
31 | if encoder is not None:
32 | self.add_encoder(encoder)
33 | else:
34 | self.add_encoder(DefaultWXFEncoder())
35 |
36 | self.default = default
37 |
38 | def add_encoder(self, *encoders):
39 | """Add a new encoder to be called last if all others failed to encode an object."""
40 | for encoder in encoders:
41 | if isinstance(encoder, WXFEncoder):
42 | self.encoders.append(encoder)
43 | encoder._provider = self
44 | else:
45 | raise TypeError("Invalid encoder.")
46 | return self
47 |
48 | def provide_wxfexpr(self, o):
49 | """Main function, a generator of wxf expr."""
50 | yield from self._iter(o)
51 |
52 | def _iter(self, o, use_default=True):
53 | """Try to encode a given expr using encoders, if none was able to
54 | process the expr and a `default` function was provided, applies it
55 | and try again. Otherwise fail.
56 | """
57 | for encoder in self.encoders:
58 | try:
59 | for sub in encoder._encode(o):
60 | yield sub
61 | return
62 | # the expression was not encoded. Moving on to the next encoder.
63 | except NotEncodedException:
64 | pass
65 |
66 | if use_default and self.default is not None:
67 | for sub in self._iter(self.default(o), use_default=False):
68 | yield sub
69 | else:
70 | raise TypeError(
71 | "Object of type %s is not serializable to WXF." % o.__class__.__name__
72 | )
73 |
--------------------------------------------------------------------------------
/wolframclient/serializers/wxfencoder/wxfnumpyencoder.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.serializers.wxfencoder.constants import ARRAY_TYPES
4 | from wolframclient.serializers.wxfencoder.wxfencoder import WXFEncoder
5 | from wolframclient.serializers.wxfencoder.wxfexpr import (
6 | WXFExprNumericArray,
7 | WXFExprPackedArray,
8 | )
9 | from wolframclient.utils.api import numpy
10 |
11 | __all__ = ["NumPyWXFEncoder"]
12 |
13 |
14 | class NumPyWXFEncoder(WXFEncoder):
15 | """
16 | NumPy array encoder. Encode numpy array as instances of packed array and / or raw array.
17 | By default only packed arrays are generated. Unsigned integer data are cast to a type that
18 | can fit the maximum value.
19 |
20 | It's possible to add support for raw arrays only for unsigned data, in which case both
21 | `packed_array_support` and `numeric_array_support` must be true.
22 |
23 | >>> NumPyWXFEncoder(packed_array_support=True, numeric_array_support=True)
24 |
25 | Finally it's possible to only output raw arrays with:
26 |
27 | >>> NumPyWXFEncoder(packed_array_support=False, numeric_array_support=True)
28 |
29 | """
30 |
31 | def __init__(self, packed_array_support=True, numeric_array_support=False):
32 | if not packed_array_support and not numeric_array_support:
33 | raise ValueError(
34 | "At least one of the two parameters packed_array_support or numeric_array_support must be True."
35 | )
36 | self.packed_array_support = packed_array_support
37 | self.numeric_array_support = numeric_array_support
38 |
39 | def encode(self, python_expr):
40 | if isinstance(python_expr, numpy.ndarray):
41 | if self.packed_array_support:
42 | array_class = WXFExprPackedArray
43 | else:
44 | array_class = WXFExprNumericArray
45 |
46 | if python_expr.dtype == numpy.int8:
47 | value_type = ARRAY_TYPES.Integer8
48 | data = python_expr.astype("")
47 | self.assertEqual(force_text(b"a\xc3\xa0"), "aà")
48 |
49 | self.assertEqual(force_text(memoryview(b"abc")), "abc")
50 | self.assertEqual(force_text(bytearray(b"abc")), "abc")
51 |
52 | self.assertEqual(force_bytes(b"abc"), b"abc")
53 | self.assertEqual(force_bytes(abs), b"")
54 | self.assertEqual(force_bytes("aà"), b"a\xc3\xa0")
55 |
56 | self.assertEqual(force_text(force_bytes("aà")), "aà")
57 |
58 | self.assertEqual(force_bytes(memoryview(b"abc")), b"abc")
59 | self.assertEqual(force_bytes(bytearray(b"abc")), b"abc")
60 |
61 | def test_dispatch(self):
62 |
63 | normalizer = Dispatch()
64 |
65 | class Person:
66 | pass
67 |
68 | class SportPlayer(Person):
69 | pass
70 |
71 | class FootballPlayer(SportPlayer):
72 | pass
73 |
74 | @normalizer.dispatch(object)
75 | def implementation(o):
76 | return o
77 |
78 | @normalizer.dispatch((int, float))
79 | def implementation(o):
80 | return o * 2
81 |
82 | @normalizer.dispatch(six.text_type)
83 | def implementation(o):
84 | return "Hello %s" % o
85 |
86 | @normalizer.dispatch(Person)
87 | def implementation(o):
88 | return "Hello person"
89 |
90 | @normalizer.dispatch(FootballPlayer)
91 | def implementation(o):
92 | return "Hello football player"
93 |
94 | self.assertEqual(normalizer("Ric"), "Hello Ric")
95 | self.assertEqual(normalizer(2), 4)
96 | self.assertEqual(normalizer(None), None)
97 |
98 | self.assertEqual(normalizer(Person()), "Hello person")
99 | self.assertEqual(normalizer(SportPlayer()), "Hello person")
100 | self.assertEqual(normalizer(FootballPlayer()), "Hello football player")
101 |
102 | normalizer.unregister(six.text_type)
103 | normalizer.register(lambda s: "Goodbye %s" % s, six.text_type)
104 |
105 | self.assertEqual(normalizer("Ric"), "Goodbye Ric")
106 |
107 | normalizer.unregister(object)
108 | normalizer.register(lambda s: 5, object)
109 |
110 | self.assertEqual(normalizer(None), 5)
111 |
112 | def test_class_dispatch(self):
113 |
114 | normalizer = Dispatch()
115 |
116 | @normalizer.dispatch(object)
117 | def implementation(self, o):
118 | return o
119 |
120 | @normalizer.dispatch(six.text_type)
121 | def implementation(self, o):
122 | return "Hello %s" % o
123 |
124 | @normalizer.dispatch(int)
125 | def implementation(self, o):
126 | return o * 2
127 |
128 | @normalizer.dispatch(int, replace_existing=True)
129 | def implementation(self, o):
130 | return o * 3
131 |
132 | class Foo:
133 | attr = normalizer.as_method()
134 |
135 | self.assertEqual(Foo().attr("Ric"), "Hello Ric")
136 | self.assertEqual(Foo().attr(2), 6)
137 | self.assertEqual(Foo().attr(None), None)
138 |
139 | @normalizer.dispatch(int, keep_existing=True)
140 | def implementation(self, o):
141 | return o * 4
142 |
143 | self.assertEqual(Foo().attr(2), 6)
144 |
145 | # inconsistent option values
146 | with self.assertRaises(ValueError):
147 |
148 | @normalizer.dispatch(int, replace_existing=True, keep_existing=True)
149 | def implementation(self, o):
150 | return o * 4
151 |
152 | # already mapped.
153 | with self.assertRaises(TypeError):
154 |
155 | @normalizer.dispatch(int)
156 | def implementation(self, o):
157 | return o * 4
158 |
--------------------------------------------------------------------------------
/wolframclient/tests/data/10ct_32bit_128.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/wolframclient/tests/data/10ct_32bit_128.tiff
--------------------------------------------------------------------------------
/wolframclient/tests/data/16_bit_binary_pgm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/wolframclient/tests/data/16_bit_binary_pgm.png
--------------------------------------------------------------------------------
/wolframclient/tests/data/32x2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/wolframclient/tests/data/32x2.png
--------------------------------------------------------------------------------
/wolframclient/tests/data/500x200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/wolframclient/tests/data/500x200.png
--------------------------------------------------------------------------------
/wolframclient/tests/data/5x2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/wolframclient/tests/data/5x2.png
--------------------------------------------------------------------------------
/wolframclient/tests/data/allbytes.wl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/wolframclient/tests/data/allbytes.wl
--------------------------------------------------------------------------------
/wolframclient/tests/data/allbytes.wxf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/wolframclient/tests/data/allbytes.wxf
--------------------------------------------------------------------------------
/wolframclient/tests/data/allchars.wxf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/wolframclient/tests/data/allchars.wxf
--------------------------------------------------------------------------------
/wolframclient/tests/data/hopper.ppm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/wolframclient/tests/data/hopper.ppm
--------------------------------------------------------------------------------
/wolframclient/tests/data/pal1wb.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/wolframclient/tests/data/pal1wb.bmp
--------------------------------------------------------------------------------
/wolframclient/tests/data/pil_sample_cmyk.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/wolframclient/tests/data/pil_sample_cmyk.jpg
--------------------------------------------------------------------------------
/wolframclient/tests/data/umbrellaRGBA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WolframResearch/WolframClientForPython/1d77447e08ff1e829270bf1933b28335a0d25e02/wolframclient/tests/data/umbrellaRGBA.png
--------------------------------------------------------------------------------
/wolframclient/tests/deserializers/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
--------------------------------------------------------------------------------
/wolframclient/tests/evaluation/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import fnmatch
4 | import os
5 |
6 | from wolframclient.utils import six
7 |
8 | # The evaluation modules is only supported on python 3.5+, because of asyncio
9 | # We need to prevent the test suite from loading this module containing coroutines.
10 | if six.PY_35:
11 | from wolframclient.tests.evaluation import test_async_cloud as m4
12 | from wolframclient.tests.evaluation import test_cloud as m1
13 | from wolframclient.tests.evaluation import test_coroutine as m3
14 | from wolframclient.tests.evaluation import test_kernel as m2
15 |
16 | test_modules = (m1, m2, m3, m4)
17 | else:
18 | test_modules = ()
19 |
20 | __all__ = ["load_tests"]
21 |
22 |
23 | def load_tests(loader, tests, pattern):
24 | suite = unittest.TestSuite()
25 | for module in test_modules:
26 | if fnmatch.fnmatch(os.path.basename(module.__file__), pattern):
27 | tests = loader.loadTestsFromModule(module)
28 | suite.addTests(tests)
29 | return suite
30 |
--------------------------------------------------------------------------------
/wolframclient/tests/evaluation/api.m:
--------------------------------------------------------------------------------
1 | (*
2 | This file contains the list of APIs used in the test suite.
3 | It should be used to deploy them on a given account to avoid
4 | false negative testing results.
5 | *)
6 |
7 | If[Not@$CloudConnected,
8 | Return[$Failed]
9 | ]
10 |
11 | co = CloudObject["api/private/requesterid"];
12 | CloudDeploy[
13 | APIFunction[
14 | {},
15 | $RequesterWolframID &
16 | ],
17 | co
18 | ]
19 |
20 | co = CloudObject["api/private/stringreverse"];
21 | CloudDeploy[
22 | APIFunction[
23 | {"str" -> "String"},
24 | StringReverse[#str] &
25 | ],
26 | co
27 | ]
28 |
29 |
30 | co = CloudObject["api/public/permkey_stringreverse_wxf"];
31 | CloudDeploy[
32 | APIFunction[
33 | {"str" -> "String"},
34 | StringReverse[#str] &,
35 | "WXF"
36 | ],
37 | co,
38 | Permissions -> {PermissionsKey["my_key"] -> "Execute"}
39 | ]
40 |
41 | co = CloudObject["api/private/range/formated/json"];
42 | CloudDeploy[APIFunction[
43 | {"max" -> "Integer", "min" -> "Integer" -> 1,
44 | "step" -> "Integer" -> 1},
45 | Range[#min, #max, #step] &,
46 | "JSON"],
47 | co
48 | ]
49 |
50 | co = CloudObject["api/public/jsonrange"];
51 | CloudDeploy[
52 | APIFunction[
53 | {"i"->"Integer"},
54 | Range[#i]&,
55 | "JSON"
56 | ],
57 | Permissions->"Public"
58 | ];
59 |
60 | CloudDeploy[
61 | APIFunction["i" -> "Integer", Range[#i] &, "XML"],
62 | CloudObject["api/private/rangeXML"]
63 | ];
64 |
65 | co = CloudObject["api/private/range/wlerror"];
66 | CloudDeploy[APIFunction[
67 | {"i"},
68 | Range[#i] &,
69 | "JSON"],
70 | co
71 | ];
72 |
73 | co = CloudObject["api/private/dimensions"];
74 | CloudDeploy[APIFunction[
75 | {"data" -> "RawJSON"},
76 | Dimensions[#data] &,
77 | "JSON"],
78 | co
79 | ];
80 |
81 | CloudDeploy[
82 | APIFunction["size" -> "Integer", RandomImage[1, #size] &, "PNG"],
83 | CloudObject["api/private/randomimagepng"]
84 | ]
85 |
86 | CloudDeploy[
87 | APIFunction[{"image" -> "Image"}, ImageDimensions[#image] &, "JSON"],
88 | CloudObject["api/private/imagedimensions"]
89 | ];
90 |
91 | api = APIFunction[{"str" -> "String", "image" -> "Image",
92 | "int" -> "Integer"},
93 | {#str, ImageDimensions[#image], #int} &,
94 | "JSON"
95 | ];
96 | CloudDeploy[api, CloudObject["api/private/str_image_int"]];
97 |
98 | CloudDeploy[
99 | APIFunction[{"x" -> "String", "y" -> "String"},
100 | {#x, #y} &,
101 | "WXF"
102 | ],
103 | CloudObject["api/private/two_parameters_out_wxf"]
104 | ];
105 |
106 | CloudDeploy[
107 | APIFunction[{"x" -> "String", "y" -> "String"},
108 | {#x, #y} &
109 | ],
110 | CloudObject["api/private/two_parameters_out_default"]
111 | ];
112 |
113 | CloudDeploy[
114 | APIFunction[{"x" -> "String", "y" -> "String"},
115 | {#x, #y} &,
116 | "JSON"
117 | ],
118 | CloudObject["api/private/two_parameters_out_json"]
119 | ];
120 |
--------------------------------------------------------------------------------
/wolframclient/tests/externalevaluate/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
--------------------------------------------------------------------------------
/wolframclient/tests/externalevaluate/ev_loop.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from threading import Thread
4 |
5 | import zmq
6 |
7 | from wolframclient.language import wl
8 | from wolframclient.serializers import export
9 | from wolframclient.utils.externalevaluate import start_zmq_loop
10 | from wolframclient.utils.tests import TestCase as BaseTestCase
11 |
12 |
13 | def create_evaluation_command(
14 | string, context={}, hook_symbol=wl.ExternalEvaluate.Private.ExternalEvaluateCommand
15 | ):
16 | return hook_symbol("Eval", (string, context))
17 |
18 |
19 | class TestCase(BaseTestCase):
20 |
21 | starting_port = 9000
22 |
23 | def compare(self, string_version, result):
24 | self.assertEqual(string_version, export(result, target_format="wxf"))
25 |
26 | def _test_using_loop(self, messages, port):
27 |
28 | def threaded_function(port=port, message_limit=len(messages)):
29 | start_zmq_loop(port=port, message_limit=message_limit)
30 |
31 | thread = Thread(target=threaded_function)
32 | thread.start()
33 |
34 | client = zmq.Context().socket(zmq.PAIR)
35 | client.connect("tcp://127.0.0.1:%s" % port)
36 |
37 | for message, context, result in messages:
38 |
39 | client.send(
40 | export(create_evaluation_command(message, context), target_format="wxf")
41 | )
42 |
43 | msg = client.recv()
44 |
45 | self.compare(msg, result)
46 |
47 | def test_variable_assign(self):
48 | self._test_using_loop((("a = 2", {}, wl.Null), ("a", {}, 2)), 12345)
49 |
50 | def test_eval_with_context(self):
51 | self._test_using_loop((("a = 3", {}, wl.Null), ("a", {"a": 2}, 2)), 12346)
52 |
53 | def test_eval_with_delete(self):
54 | self._test_using_loop(
55 | (("a = 3", {}, wl.Null), ("del a", {}, wl.Null), ("'a' in globals()", {}, False)),
56 | 12347,
57 | )
58 |
--------------------------------------------------------------------------------
/wolframclient/tests/local_config_sample.json:
--------------------------------------------------------------------------------
1 | // this json file is used by the test suite to connect to the Wolfram public cloud
2 | // and to evaluate expressions to a local kernel.
3 | // It's path is specified with the environment variable WOLFRAMCLIENT_PY_JSON_CONFIG
4 | {
5 | "kernel" : "/path/to/WolframKernel",
6 | "cloud_credentials" : {
7 | "User": {
8 | "id": "$WolframID",
9 | "password": "my password"
10 | },
11 | "SAK": {
12 | "consumer_key": "consumer key",
13 | "consumer_secret": "consumer secret"
14 | }
15 | },
16 | "ApiOwner": "$UserName",
17 | "server": {
18 | "host": "https://www.my.wolframcloud.com",
19 | "request_token_endpoint": "https://account.my.wolfram.cloud.com/auth/request-token",
20 | "access_token_endpoint": "https://account.my.wolfram.cloud.com/auth/access-token",
21 | "xauth_consumer_key": "my xauth consumer key (optional)",
22 | "xauth_consumer_secret": "my xauth consumer secret (optional)",
23 | "certificate" : "/path/to/certificate/ (optional)"
24 | }
25 | }
--------------------------------------------------------------------------------
/wolframclient/tests/log/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/wolframclient/tests/serializers/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
--------------------------------------------------------------------------------
/wolframclient/tests/serializers/encoder.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.serializers import export, wolfram_encoder
4 | from wolframclient.utils.tests import TestCase as BaseTestCase
5 |
6 |
7 | class foo:
8 | pass
9 |
10 |
11 | class subfoo(foo):
12 | pass
13 |
14 |
15 | class subsubfoo(subfoo):
16 | pass
17 |
18 |
19 | class subsubfoo2(subfoo):
20 | pass
21 |
22 |
23 | class bar:
24 | pass
25 |
26 |
27 | class TestCase(BaseTestCase):
28 | @classmethod
29 | def setUpClass(cls):
30 | @wolfram_encoder.dispatch(foo)
31 | def encode_foo(s, o):
32 | return s.serialize_symbol(b"foo")
33 |
34 | @wolfram_encoder.dispatch(subfoo)
35 | def encode_subfoo(s, o):
36 | return s.serialize_symbol(b"subfoo")
37 |
38 | @wolfram_encoder.dispatch(subsubfoo)
39 | def encode_subsubfoo(s, o):
40 | return s.serialize_symbol(b"subsubfoo")
41 |
42 | @classmethod
43 | def tearDownClass(cls):
44 | wolfram_encoder.unregister((foo, subfoo, subsubfoo))
45 |
46 | def test_encode_parent_class(self):
47 | wl = export(foo())
48 | self.assertEqual(wl, b"foo")
49 |
50 | def test_encode_sub_class(self):
51 | wl = export(subfoo())
52 | self.assertEqual(wl, b"subfoo")
53 |
54 | def test_encode_sub_sub_class(self):
55 | wl = export(subsubfoo())
56 | self.assertEqual(wl, b"subsubfoo")
57 |
58 | def test_encode_sub_sub_class_no_mapping(self):
59 | wl = export(subsubfoo2())
60 | self.assertEqual(wl, b"subfoo")
61 |
62 | def test_encode_class_mix(self):
63 | wl = export([subfoo(), foo(), subsubfoo2()])
64 | self.assertEqual(wl, b"{subfoo, foo, subfoo}")
65 |
66 | def test_encode_not_registered(self):
67 | with self.assertRaises(NotImplementedError):
68 | export(bar())
69 |
70 | def test_register_twice_no_force(self):
71 | with self.assertRaises(TypeError):
72 |
73 | @wolfram_encoder.dispatch(subsubfoo)
74 | def encode_subsubfoo_again(s, o):
75 | return s.serialize_symbol("subsubfooAGAIN")
76 |
77 | def test_register_twice_force(self):
78 | @wolfram_encoder.dispatch(subsubfoo, replace_existing=True)
79 | def encode_subsubfoo_again(s, o):
80 | return s.serialize_symbol("subsubfooFORCE")
81 |
82 | wl = export(subsubfoo())
83 | self.assertEqual(wl, b"subsubfooFORCE")
84 |
--------------------------------------------------------------------------------
/wolframclient/tests/serializers/wl_export.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import os
4 | import tempfile
5 |
6 | from wolframclient.language import wl
7 | from wolframclient.serializers import available_formats, export
8 | from wolframclient.utils import six
9 | from wolframclient.utils.functional import identity
10 | from wolframclient.utils.tests import TestCase as BaseTestCase
11 |
12 |
13 | class TestCase(BaseTestCase):
14 | def test_export(self):
15 |
16 | # checking that export is able to return bytes if no second argument is provided
17 |
18 | self.assertEqual(export(2), b"2")
19 | self.assertEqual(export("foo"), b'"foo"')
20 |
21 | fd, path = tempfile.mkstemp()
22 | # close the file descriptor but keep the path. Prevent error on Windows.
23 | os.close(fd)
24 |
25 | for test in ["foo", wl.Symbol, {"a": [1, 2, 3], 2: 2}]:
26 |
27 | for export_format in available_formats:
28 |
29 | expected = export(test, target_format=export_format)
30 |
31 | # checking that export is able to write to a path if a string is provided
32 |
33 | export(test, path, target_format=export_format)
34 |
35 | with open(path, "rb") as stream:
36 | self.assertEqual(stream.read(), expected)
37 |
38 | # checking that export is writing to a byteio
39 |
40 | stream = six.BytesIO()
41 |
42 | export(test, stream, target_format=export_format)
43 |
44 | stream.seek(0)
45 |
46 | self.assertEqual(stream.read(), expected)
47 |
48 | # checking that export is able to write to a filelike object
49 |
50 | with open(path, "wb") as stream:
51 | export(test, stream, target_format=export_format)
52 |
53 | with open(path, "rb") as stream:
54 | self.assertEqual(stream.read(), expected)
55 |
56 | os.remove(path)
57 |
58 | def test_serialization_custom(self):
59 | class MyStuff:
60 | def __init__(self, *stuff):
61 | self.stuff = stuff
62 |
63 | def normalizer(o):
64 | if isinstance(o, six.integer_types):
65 | return "o"
66 | if isinstance(o, MyStuff):
67 | return wl.RandomThings(*o.stuff)
68 | return o
69 |
70 | expr = [1, 2, "a", {1: "a"}, MyStuff(1, 2, MyStuff(1, "a"))]
71 | normalized = [
72 | "o",
73 | "o",
74 | "a",
75 | {"o": "a"},
76 | wl.RandomThings("o", "o", wl.RandomThings("o", "a")),
77 | ]
78 |
79 | for export_format in available_formats:
80 |
81 | with self.assertRaises(NotImplementedError):
82 | export(expr, normalizer=identity, target_format=export_format)
83 |
84 | self.assertEqual(
85 | export(expr, normalizer=normalizer, target_format=export_format),
86 | export(normalized, normalizer=identity, target_format=export_format),
87 | )
88 |
89 | def test_export_with_encoder(self):
90 |
91 | # very similar code is used by safe_wl_execute, we need to make sure that we can pass the builtin encoder and a very simple
92 | # data dump and the code keeps working
93 |
94 | self.assertEqual(
95 | export(
96 | wl.Failure("PythonFailure", {"MessageTemplate": ("baz", "bar")}),
97 | target_format="wl",
98 | encoder="wolframclient.serializers.encoders.builtin.encoder",
99 | ),
100 | b'Failure["PythonFailure", <|"MessageTemplate" -> {"baz", "bar"}|>]',
101 | )
102 |
--------------------------------------------------------------------------------
/wolframclient/tests/serializers/wl_pil.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.serializers import export
4 | from wolframclient.utils.api import PIL, numpy
5 | from wolframclient.utils.tests import TestCase as BaseTestCase
6 | from wolframclient.utils.tests import path_to_file_in_data_dir
7 |
8 |
9 | class TestCase(BaseTestCase):
10 | def test_png_mode_I(self):
11 |
12 | with PIL.open(path_to_file_in_data_dir("5x2.png")) as image:
13 |
14 | self.assertEqual(
15 | export(image, target_format="wl"),
16 | b'ImportByteArray[ByteArray["iVBORw0KGgoAAAANSUhEUgAAAAUAAAACEAAAAADlkZXCAAAAH0lEQVR4nGP0+P39rf6+ky9/R7Aoen2+9shDWSRCHwCO7ws73c3PRQAAAABJRU5ErkJggg=="], "PNG"]',
17 | )
18 |
19 | def test_mode_L(self):
20 | a = numpy.arange(10).reshape((2, 5)).astype(numpy.int8)
21 | img = PIL.fromarray(a, mode="L")
22 | out = export(img, target_format="wl")
23 | self.assertEqual(
24 | out,
25 | b'Image[BinaryDeserialize[ByteArray["ODrCEAICBQABAgMEBQYHCAk="]], "Byte", Rule[ColorSpace, "Grayscale"], Rule[Interleaving, True]]',
26 | )
27 |
28 | def test_bool_img(self):
29 | a = numpy.array([[1, 0], [0, 1]], dtype="bool")
30 | img = PIL.fromarray(a)
31 | out = export(img, target_format="wl")
32 | self.assertEqual(
33 | out,
34 | b'Image[BinaryDeserialize[ByteArray["ODrCEAICAgEAAAE="]], "Bit", Rule[ColorSpace, Automatic], Rule[Interleaving, True]]',
35 | )
36 |
--------------------------------------------------------------------------------
/wolframclient/tests/serializers/wxf_compress.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals, with_statement
2 |
3 | import random
4 | import zlib
5 |
6 | from wolframclient.serializers.wxfencoder.streaming import (
7 | ExactSizeReader,
8 | ZipCompressedReader,
9 | ZipCompressedWriter,
10 | )
11 | from wolframclient.utils import six
12 | from wolframclient.utils.tests import TestCase as BaseTestCase
13 |
14 | if six.PY2:
15 |
16 | def _bytes(value):
17 | return chr(value)
18 |
19 | else:
20 |
21 | def _bytes(value):
22 | return bytes((value,))
23 |
24 |
25 | class TestCase(BaseTestCase):
26 | def test_compress(self):
27 | stream = six.BytesIO()
28 | with ZipCompressedWriter(stream) as z_writer:
29 | z_writer.write(b"abc")
30 | zipped = b"x\x9cKLJ\x06\x00\x02M\x01'"
31 | self.assertSequenceEqual(stream.getvalue(), zipped)
32 |
33 | def test_multi_write_compress(self):
34 | byte_list = [random.randint(0, 255) for i in range(10000)]
35 | data = six.binary_type(bytearray(byte_list))
36 | stream = six.BytesIO()
37 | with ZipCompressedWriter(stream) as z_writer:
38 | for i in byte_list:
39 | z_writer.write(_bytes(i))
40 | zipped = zlib.compress(data)
41 | self.assertSequenceEqual(stream.getvalue(), zipped)
42 |
43 | def test_uncompress(self):
44 | byte_list = [random.randint(0, 255) for i in range(10000)]
45 | data = six.binary_type(bytearray(byte_list))
46 | zipped = zlib.compress(data)
47 | total = len(zipped)
48 | num_of_chunk = 20
49 | chunk_size = total // num_of_chunk
50 | in_buffer = six.BytesIO(zipped)
51 | reader = ZipCompressedReader(in_buffer)
52 | buff = six.BytesIO()
53 | for i in range(num_of_chunk):
54 | buff.write(reader.read(chunk_size))
55 | self.assertEqual(buff.getvalue(), data[: (i + 1) * chunk_size])
56 |
57 | buff.write(reader.read())
58 | self.assertEqual(buff.getvalue(), data)
59 | self.assertEqual(reader.read(), b"")
60 |
61 | def test_uncompress_exact_len(self):
62 | byte_list = [random.randint(0, 255) for i in range(10000)]
63 | data = six.binary_type(bytearray(byte_list))
64 | zipped = zlib.compress(data)
65 | total = len(zipped)
66 | num_of_chunk = 20
67 | chunk_size = total // num_of_chunk
68 | in_buffer = six.BytesIO(zipped)
69 | reader = ExactSizeReader(ZipCompressedReader(in_buffer))
70 | buff = six.BytesIO()
71 | for i in range(num_of_chunk):
72 | buff.write(reader.read(chunk_size))
73 | self.assertEqual(buff.getvalue(), data[: (i + 1) * chunk_size])
74 |
75 | buff.write(reader.read())
76 | self.assertEqual(buff.getvalue(), data)
77 |
78 | def test_uncompress_exact_len_err(self):
79 | data = six.binary_type(bytearray(range(100)))
80 | zipped = zlib.compress(data)
81 | total = len(zipped)
82 | reader = ExactSizeReader(ZipCompressedReader(six.BytesIO(zipped)))
83 | with self.assertRaises(EOFError):
84 | reader.read(size=total + 1)
85 |
--------------------------------------------------------------------------------
/wolframclient/tests/serializers/wxf_encoder.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import os
4 |
5 | from wolframclient.serializers.wxfencoder.serializer import WXFExprSerializer
6 | from wolframclient.serializers.wxfencoder.wxfencoder import WXFEncoder
7 | from wolframclient.serializers.wxfencoder.wxfexpr import WXFExprFunction, WXFExprSymbol
8 | from wolframclient.serializers.wxfencoder.wxfexprprovider import WXFExprProvider
9 | from wolframclient.tests.configure import dir_test_data
10 | from wolframclient.utils import six
11 | from wolframclient.utils.tests import TestCase as BaseTestCase
12 |
13 |
14 | class MyClass:
15 | def __init__(self, *values):
16 | self.values = values
17 |
18 |
19 | class MyClass1(MyClass):
20 | def __init__(self, *values):
21 | super().__init__(*values)
22 |
23 |
24 | class MyClass2(MyClass):
25 | def __init__(self, *values):
26 | super().__init__(*values)
27 |
28 |
29 | class MyClassEncoder(WXFEncoder):
30 | def encode(self, o):
31 | if isinstance(o, MyClass):
32 | yield WXFExprFunction(len(o.values))
33 | yield WXFExprSymbol("Global`%s" % o.__class__.__name__)
34 | for sub in o.values:
35 | yield from self.serialize(sub)
36 |
37 |
38 | class TestCase(BaseTestCase):
39 | def test_custom_encoder(self):
40 | """test re-entrant calls"""
41 | expr_provider = WXFExprProvider()
42 | expr_provider.add_encoder(MyClassEncoder())
43 | stream = six.BytesIO()
44 | serializer = WXFExprSerializer(stream, expr_provider=expr_provider)
45 | myclass2 = MyClass2(True, "foobar")
46 | myclass1 = MyClass1(1, None)
47 | myclass = MyClass1(1, 2, [myclass2, myclass1])
48 | o = ["foo", [1, {"k1": myclass, "k2": False}]]
49 | serializer.serialize(o)
50 |
51 | data_dir = dir_test_data()
52 | filepath = os.path.join(data_dir, "test.wxf")
53 | with open(filepath, "wb") as w_file:
54 | w_file.write(stream.getvalue())
55 | os.remove(filepath)
56 |
--------------------------------------------------------------------------------
/wolframclient/tests/serializers/wxf_pythonarray.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import absolute_import, print_function, unicode_literals
4 |
5 | from wolframclient.language.array import NumericArray
6 | from wolframclient.serializers import export
7 | from wolframclient.utils.api import numpy
8 | from wolframclient.utils.tests import TestCase as BaseTestCase
9 |
10 |
11 | class TestCase(BaseTestCase):
12 | def test_python_array(self):
13 |
14 | for array, numpy_type, wl_type in (
15 | ([True, False, True, False, True, False], numpy.int8, "Integer8"),
16 | ([1, 2, 3, 4, 5, 6], numpy.int8, "Integer8"),
17 | ([1, 2, 3, 4, 5, 6], numpy.int32, "Integer32"),
18 | ([1, 2, 3, 4, 5, 6], numpy.int64, "Integer64"),
19 | ([1.2, 2.3, 3, 4, 5, 6], numpy.float32, "Real32"),
20 | ([1.2, 2.3, 3, 4, 5, 6], numpy.float64, "Real64"),
21 | ):
22 |
23 | for shape in ((3, 2), None):
24 |
25 | arr = numpy.array(array, numpy_type)
26 | if shape:
27 | arr = arr.reshape(shape)
28 |
29 | self.assertEqual(
30 | export(arr, target_format="wxf"),
31 | export(NumericArray(array, wl_type, shape=shape), target_format="wxf"),
32 | )
33 |
34 | def test_generators(self):
35 |
36 | array = NumericArray((i for i in range(10)), "Integer8", shape=(10,))
37 |
38 | self.assertEqual(
39 | export(numpy.arange(10).astype(numpy.int8), target_format="wxf"),
40 | export(array, target_format="wxf"),
41 | )
42 |
43 | self.assertEqual(len(array), 10)
44 |
45 | array = NumericArray((i for i in range(10)), "Integer8", shape=(5, 2))
46 |
47 | self.assertEqual(
48 | export(numpy.arange(10).astype(numpy.int8).reshape(5, 2), target_format="wxf"),
49 | export(array, target_format="wxf"),
50 | )
51 |
52 | self.assertEqual(len(array), 10)
53 |
--------------------------------------------------------------------------------
/wolframclient/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.utils.logger import str_trim
4 | from wolframclient.utils.tests import TestCase as BaseTestCase
5 |
6 |
7 | class TestCase(BaseTestCase):
8 | def test_str_trim_no_limit(self):
9 | self.assertEqual(str_trim("abcde"), "abcde")
10 |
11 | def test_str_trim_above_limit(self):
12 | self.assertEqual(str_trim("abcde", max_char=3), "abc...(2 more)")
13 |
14 | def test_str_trim_eq_limit(self):
15 | self.assertEqual(str_trim("abc", max_char=3), "abc")
16 |
--------------------------------------------------------------------------------
/wolframclient/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
--------------------------------------------------------------------------------
/wolframclient/utils/asyncio.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import asyncio
4 |
5 | from wolframclient.utils.decorators import decorate
6 |
7 |
8 | def get_event_loop(loop=None):
9 | try:
10 | return loop or asyncio.get_event_loop()
11 | except RuntimeError:
12 | loop = asyncio.new_event_loop()
13 | asyncio.set_event_loop(loop)
14 | return loop
15 |
16 |
17 | def run(coro):
18 | return get_event_loop().run_until_complete(coro)
19 |
20 |
21 | run_in_loop = decorate(run)
22 |
23 |
24 | def create_task(coro):
25 | """ensure_future using get_event_loop, so that it behaves similarly to
26 | create_task, and gets the same signature.
27 | """
28 | return asyncio.ensure_future(coro, loop=get_event_loop())
29 |
--------------------------------------------------------------------------------
/wolframclient/utils/datastructures.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from collections import OrderedDict
4 |
5 |
6 | class Association(OrderedDict):
7 | """A :class:`~collections.OrderedDict` that serializes to an Association"""
8 |
9 | def __repr__(self):
10 | return dict.__repr__(self)
11 |
12 |
13 | def _fail(self, *args, **opts):
14 | raise TypeError("{} does not support item assignment".format(self.__class__.__name__))
15 |
16 |
17 | class immutabledict(dict):
18 | """
19 | hashable dict implementation, suitable for use as a key into
20 | other dicts.
21 |
22 | >>> h1 = immutabledict({"apples": 1, "bananas":2})
23 | >>> h2 = immutabledict({"bananas": 3, "mangoes": 5})
24 | >>> h1+h2
25 | immutabledict(apples=1, bananas=3, mangoes=5)
26 | >>> d1 = {}
27 | >>> d1[h1] = "salad"
28 | >>> d1[h1]
29 | 'salad'
30 | >>> d1[h2]
31 | Traceback (most recent call last):
32 | ...
33 | KeyError: immutabledict(bananas=3, mangoes=5)
34 |
35 | based on answers from
36 | http://stackoverflow.com/questions/1151658/python-hashable-dicts
37 |
38 | """
39 |
40 | def __hash__(self):
41 | return hash(tuple(self.items()))
42 |
43 | __setitem__ = _fail
44 | __delitem__ = _fail
45 | clear = _fail
46 | pop = _fail
47 | popitem = _fail
48 | setdefault = _fail
49 | update = _fail
50 |
51 | def __add__(self, right):
52 | result = hashdict(self)
53 | dict.update(result, right)
54 | return result
55 |
56 |
57 | class Settings(dict):
58 | """
59 | Dictionary subclass enabling attribute lookup/assignment of keys/values.
60 |
61 | For example::
62 |
63 | >>> m = Settings({'foo': 'bar'})
64 | >>> m.foo
65 | 'bar'
66 | >>> m.foo = 'not bar'
67 | >>> m['foo']
68 | 'not bar'
69 |
70 | ``Settings`` objects also provide ``.first()`` which acts like
71 | ``.get()`` but accepts multiple keys as arguments, and returns the value of
72 | the first hit, e.g.::
73 |
74 | >>> m = Settings({'foo': 'bar', 'biz': 'baz'})
75 | >>> m.first('wrong', 'incorrect', 'foo', 'biz')
76 | 'bar'
77 |
78 | """
79 |
80 | def __getattr__(self, key):
81 | try:
82 | return self[key]
83 | except KeyError:
84 | # to conform with __getattr__ spec
85 | raise AttributeError(key)
86 |
87 | def __setattr__(self, key, value):
88 | self[key] = value
89 |
--------------------------------------------------------------------------------
/wolframclient/utils/debug.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from decimal import Decimal
4 | from functools import wraps
5 |
6 | from wolframclient.utils.api import time
7 |
8 |
9 | def timed(function):
10 | def inner(*args, **opts):
11 | t = time.perf_counter()
12 | value = function(*args, **opts)
13 | return time.perf_counter() - t, value
14 |
15 | return inner
16 |
17 |
18 | def echo(x):
19 | print(x)
20 | return x
21 |
22 |
23 | def timed_repeated(N=30):
24 | def repeated(function):
25 | def inner(*args, **opts):
26 | return repeated_timing(function, *args, N=N, **opts)
27 |
28 | return inner
29 |
30 | return repeated
31 |
32 |
33 | def repeated_timing(function, *args, **opts):
34 | N = opts.pop("N", 30)
35 | timed_func = timed(function)
36 | timers = []
37 | for _ in range(N):
38 | timer, res = timed_func(*args, **opts)
39 | timers.append(timer)
40 | timers = sorted(timers)
41 | min = int(0.1 * N)
42 | max = int(0.9 * N)
43 | trimmed_timers = timers[min:max]
44 | return sum(trimmed_timers) / len(trimmed_timers), res
45 |
46 |
47 | def print_elapsed_time(viewfunc):
48 | @wraps(viewfunc)
49 | def inner(*args, **kw):
50 | t = time.perf_counter()
51 | res = viewfunc(*args, **kw)
52 | print(
53 | "Done {}: {} sec".format(
54 | inner.__name__, Decimal(time.perf_counter() - t).quantize(Decimal("0.000000"))
55 | )
56 | )
57 | return res
58 |
59 | return inner
60 |
--------------------------------------------------------------------------------
/wolframclient/utils/decorators.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from functools import wraps
4 |
5 | from wolframclient.utils.datastructures import Association
6 | from wolframclient.utils.functional import composition
7 |
8 |
9 | def decorate(*func):
10 | def annotation(fn):
11 | return wraps(fn)(composition(fn, *func))
12 |
13 | return annotation
14 |
15 |
16 | to_tuple = decorate(tuple)
17 | to_dict = decorate(Association)
18 |
19 |
20 | class cached_property:
21 | """
22 | Decorator that converts a method with a single self argument into a
23 | property cached on the instance.
24 |
25 | Optional ``name`` argument allows you to make cached properties of other
26 | methods. (e.g. url = cached_property(get_absolute_url, name='url') )
27 | """
28 |
29 | def __init__(self, func, name=None):
30 | self.func = func
31 | self.__doc__ = func.__doc__
32 | self.name = name or func.__name__
33 |
34 | def __get__(self, instance, cls=None):
35 | if instance is None:
36 | return self
37 | res = instance.__dict__[self.name] = self.func(instance)
38 | return res
39 |
--------------------------------------------------------------------------------
/wolframclient/utils/encoding.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.utils import six
4 | from wolframclient.utils.dispatch import Dispatch
5 | from wolframclient.utils.functional import map
6 |
7 | force_text = Dispatch()
8 |
9 |
10 | @force_text.dispatch(six.buffer_types)
11 | def encode(s, *args, **opts):
12 | return force_text(force_bytes(s, *args, **opts), *args, **opts)
13 |
14 |
15 | @force_text.dispatch(six.text_type)
16 | def encode(s, encoding="utf-8", errors="strict"):
17 | return s
18 |
19 |
20 | @force_text.dispatch(six.binary_type, replace_existing=True)
21 | def encode(s, encoding="utf-8", errors="strict"):
22 | return s.decode(encoding, errors)
23 |
24 |
25 | if not six.PY2:
26 |
27 | @force_text.dispatch(object)
28 | def encode(s, encoding="utf-8", errors="strict"):
29 | return six.text_type(s)
30 |
31 | else:
32 |
33 | @force_text.dispatch(object)
34 | def encode(s, encoding="utf-8", errors="strict"):
35 | if hasattr(s, "__unicode__"):
36 | return six.text_type(s)
37 | else:
38 | return six.text_type(bytes(s), encoding, errors)
39 |
40 |
41 | force_bytes = Dispatch()
42 |
43 |
44 | @force_bytes.dispatch(six.string_types)
45 | def encode(s, encoding="utf-8", errors="strict"):
46 | return s.encode(encoding, errors)
47 |
48 |
49 | @force_bytes.dispatch(six.binary_type, replace_existing=True)
50 | def encode(s, encoding="utf-8", errors="strict"):
51 | return s
52 |
53 |
54 | @force_bytes.dispatch(six.buffer_types, replace_existing=True)
55 | def encode(s, encoding="utf-8", errors="strict"):
56 | return six.binary_type(s)
57 |
58 |
59 | if six.PY2:
60 |
61 | @force_bytes.dispatch(memoryview, replace_existing=True)
62 | def encode(s, encoding="utf-8", errors="strict"):
63 | return s.tobytes()
64 |
65 |
66 | if not six.PY2:
67 |
68 | def encode_default(s, encoding="utf-8", errors="strict"):
69 | return six.text_type(s).encode(encoding)
70 |
71 | else:
72 |
73 | def encode_default(s, encoding="utf-8", errors="strict"):
74 | return six.binary_type(s)
75 |
76 |
77 | @force_bytes.dispatch(object)
78 | def encode(s, encoding="utf-8", errors="strict"):
79 | try:
80 | return encode_default(s, encoding, errors)
81 | except UnicodeEncodeError:
82 | if isinstance(s, Exception):
83 | # An Exception subclass containing non-ASCII data that doesn't
84 | # know how to print itself properly. We shouldn't raise a
85 | # further exception.
86 | return b" ".join(force_bytes(arg, encoding, errors=errors) for arg in s)
87 | return six.text_type(s).encode(encoding, errors)
88 |
89 |
90 | def safe_force_text(obj):
91 | try:
92 | return force_text(obj, errors="ignore")
93 | except Exception as e:
94 | return "" % e
95 |
96 |
97 | # this function is supposed to be the most efficient byte concatenation that can be archived in python
98 | # used by the serializers
99 |
100 | # join seems to be the winner
101 | # https://gist.github.com/smcl/7462529818bb77baad32727a9e5ff44c
102 | # https://blog.mclemon.io/python-efficient-string-concatenation-in-python-2016-edition
103 |
104 | if six.PY2:
105 |
106 | def concatenate_bytes(iterable):
107 | return b"".join(map(six.binary_type, iterable))
108 |
109 | else:
110 | concatenate_bytes = b"".join
111 |
--------------------------------------------------------------------------------
/wolframclient/utils/environment.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.utils import six
4 | from wolframclient.utils.api import os
5 |
6 |
7 | def installation_version():
8 |
9 | v = os.environ.get("WOLFRAM_KERNEL_VERSION", None)
10 | if v:
11 | return float(v)
12 |
13 | return 12.0
14 |
15 |
16 | def _explore_paths(*paths):
17 | highest_version = -1
18 | best_path = None
19 | for root in paths:
20 | if os.isdir(root):
21 | for version in os.listdir(root):
22 | full_path = os.path_join(root, version)
23 | if os.isdir(full_path):
24 | try:
25 | v_num = float(version)
26 | except ValueError:
27 | v_num = -2
28 | if v_num > highest_version and v_num > 0:
29 | highest_version = v_num
30 | best_path = full_path
31 | if best_path:
32 | yield best_path
33 |
34 |
35 | def _installation_directories():
36 | env = os.environ.get("WOLFRAM_INSTALLATION_DIRECTORY", None)
37 | if env:
38 | yield env
39 |
40 | if six.WINDOWS:
41 | for p in _explore_paths(
42 | "C:\\Program Files\\Wolfram Research\\Wolfram Desktop",
43 | "C:\\Program Files\\Wolfram Research\\Mathematica",
44 | "C:\\Program Files\\Wolfram Research\\Wolfram Engine",
45 | ):
46 | yield p
47 |
48 | elif six.LINUX:
49 | for p in _explore_paths(
50 | "/usr/local/Wolfram/Desktop",
51 | "/usr/local/Wolfram/Mathematica",
52 | "/usr/local/Wolfram/WolframEngine",
53 | ):
54 | yield p
55 |
56 | elif six.MACOS:
57 | yield "/Applications/Wolfram Desktop.app/Contents"
58 | yield "/Applications/Mathematica.app/Contents"
59 | yield "/Applications/Wolfram Engine.app/Contents"
60 |
61 |
62 | def find_default_kernel_path():
63 | """Look for the most recent installed kernel."""
64 |
65 | if six.WINDOWS:
66 | rel = "WolframKernel.exe"
67 | elif six.LINUX:
68 | rel = "Executables/WolframKernel"
69 | elif six.MACOS:
70 | rel = "MacOS/WolframKernel"
71 |
72 | for path in _installation_directories():
73 | if rel:
74 | path = os.path_join(path, rel)
75 | if os.isfile(path):
76 | return path
77 |
--------------------------------------------------------------------------------
/wolframclient/utils/functional.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import inspect
4 | from functools import reduce
5 | from itertools import chain, islice
6 |
7 | from wolframclient.utils import six
8 |
9 | if six.PY2:
10 |
11 | def map(f, iterable):
12 | return (f(e) for e in iterable)
13 |
14 | else:
15 | map = map
16 |
17 |
18 | def first(iterable, default=None):
19 | try:
20 | return next(iter(iterable))
21 | except StopIteration:
22 | return default
23 |
24 |
25 | def last(iterable, default=None):
26 | try:
27 | return iterable[-1]
28 | except IndexError:
29 | return default
30 |
31 |
32 | def identity(x):
33 | return x
34 |
35 |
36 | def composition(*functions):
37 | return reduce(
38 | lambda f, g: lambda *args, **kw: f(g(*args, **kw)), reversed(functions or (identity,))
39 | )
40 |
41 |
42 | def is_iterable(obj, exclude_list=six.string_types):
43 | if isinstance(obj, exclude_list):
44 | return False
45 | return not inspect.isclass(obj) and hasattr(obj, "__iter__")
46 |
47 |
48 | def to_iterable(obj, exclude_list=six.string_types):
49 | if isinstance(obj, exclude_list):
50 | return (obj,)
51 | try:
52 | return iter(obj)
53 | except TypeError:
54 | return (obj,)
55 |
56 |
57 | def iterate(*args):
58 | return chain.from_iterable(map(to_iterable, args))
59 |
60 |
61 | def flatten(*args):
62 | for arg in args:
63 | if is_iterable(arg):
64 | for sub in arg:
65 | yield from flatten(sub)
66 | else:
67 | yield arg
68 |
69 |
70 | def riffle(iterable, separator):
71 | iterable = iter(iterable)
72 | try:
73 | yield next(iterable)
74 | for el in iterable:
75 | yield separator
76 | yield el
77 | except StopIteration:
78 | pass
79 |
80 |
81 | def partition(iterable, n):
82 | """Yield successive n-sized chunks from l."""
83 | iterable = iter(iterable)
84 | res = tuple(islice(iterable, n))
85 | while len(res) != 0:
86 | yield res
87 | res = tuple(islice(iterable, n))
88 |
--------------------------------------------------------------------------------
/wolframclient/utils/importutils.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import os
4 | from importlib import import_module
5 |
6 | from wolframclient.utils import six
7 |
8 |
9 | def module_path(module, *args):
10 | if isinstance(module, six.string_types):
11 | try:
12 | module = import_module(module)
13 | except ImportError:
14 | return None
15 | return os.path.join(os.path.dirname(os.path.realpath(module.__file__)), *args)
16 |
17 |
18 | def import_string(dotted_path):
19 | """
20 | Import a dotted module path and return the attribute/class designated by the
21 | last name in the path. Raise ImportError if the import failed.
22 | """
23 |
24 | if not isinstance(dotted_path, six.string_types):
25 | return dotted_path
26 |
27 | try:
28 | module_path, class_name = dotted_path.rsplit(".", 1)
29 | except ValueError:
30 | raise ImportError("%s doesn't look like a module path" % dotted_path)
31 |
32 | module = import_module(module_path)
33 |
34 | if class_name == "__module__":
35 | return module
36 |
37 | try:
38 | return getattr(module, class_name)
39 | except AttributeError:
40 | raise ImportError(
41 | 'Module "{}" does not define a "{}" attribute/class'.format(
42 | module_path, class_name
43 | )
44 | )
45 |
46 |
47 | def safe_import_string(f):
48 | if isinstance(f, (list, tuple)):
49 | for path in f:
50 | try:
51 | return import_string(path)
52 | except ImportError:
53 | pass
54 | raise ImportError("Cannot import {}".format(f))
55 | if isinstance(f, six.string_types):
56 | return import_string(f)
57 | return f
58 |
59 |
60 | def safe_import_string_and_call(f, *args, **kw):
61 | return safe_import_string(f)(*args, **kw)
62 |
63 |
64 | class API:
65 | def __init__(self, importer=safe_import_string, **mapping):
66 | self.__dict__["importer"] = importer
67 | self.__dict__["mapping"] = mapping
68 | self.__dict__["imports"] = {}
69 |
70 | def __getattr__(self, value):
71 | key = self.__dict__["mapping"][value]
72 | try:
73 | return self.__dict__["imports"][key]
74 | except KeyError:
75 | self.__dict__["imports"][key] = self.__dict__["importer"](key)
76 | return self.__dict__["imports"][key]
77 |
78 | def __getitem__(self, key):
79 | try:
80 | return getattr(self, key)
81 | except AttributeError:
82 | raise KeyError("unregistred module %s" % key)
83 |
84 | def __len__(self):
85 | return len(self.__dict__["mapping"])
86 |
87 | def __bool__(self):
88 | return bool(self.__dict__["mapping"])
89 |
90 | def keys(self):
91 | return iter(self)
92 |
93 | def values(self):
94 | for key in self:
95 | yield self[key]
96 |
97 | def items(self):
98 | return zip(self.keys(), self.values())
99 |
100 | def __repr__(self):
101 | return "<{} {}>".format(self.__class__.__name__, ", ".join(sorted(self)))
102 |
103 | def __dir__(self):
104 | return list(self)
105 |
106 | def __iter__(self):
107 | return iter(self.__dict__["mapping"].keys())
108 |
--------------------------------------------------------------------------------
/wolframclient/utils/json.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import sys
4 | from json import loads as json_loads
5 |
6 | from wolframclient.utils import six
7 | from wolframclient.utils.encoding import force_text
8 |
9 | # Python 3.4 and 3.5 json loads only accept str.
10 | if sys.version_info[0] == 3 and sys.version_info[1] <= 5:
11 |
12 | def loads(s, **kwargs):
13 | if isinstance(s, six.binary_type):
14 | s = force_text(s)
15 | return json_loads(s)
16 |
17 | else:
18 | loads = json_loads
19 |
--------------------------------------------------------------------------------
/wolframclient/utils/lock.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.utils.importutils import safe_import_string_and_call
4 |
5 | try:
6 | _lock = safe_import_string_and_call("multiprocessing.Lock")
7 |
8 | def Lock():
9 | return _lock
10 |
11 | except (ImportError, OSError):
12 |
13 | # JYTHON is raising an ImportError when running "import multiprocessing"
14 | # GVisor is raising an OSError when running "multiprocessing.Lock()" because the feature is not implemented
15 |
16 | import warnings
17 | from contextlib import contextmanager
18 |
19 | warnings.warn("Lock is not implemented in the current interpreter.", RuntimeWarning)
20 |
21 | @contextmanager
22 | def Lock():
23 | yield
24 |
--------------------------------------------------------------------------------
/wolframclient/utils/logger.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import logging
4 |
5 | from wolframclient.utils import six
6 | from wolframclient.utils.encoding import force_text
7 |
8 | if six.PY2:
9 |
10 | def setup_logging_to_file(path, level=None):
11 | logging.basicConfig(
12 | filename=path,
13 | filemode="a",
14 | format="%(asctime)s, %(name)s %(levelname)s %(message)s",
15 | level=(level is not None) or logging.INFO,
16 | )
17 |
18 | else:
19 |
20 | def setup_logging_to_file(path, level=None):
21 | """Setup a basic Python logging configuration to a given file."""
22 | logging.basicConfig(
23 | format="%(asctime)s, %(name)s %(levelname)s %(message)s",
24 | handlers=[logging.FileHandler(path, mode="a", encoding="utf-8")],
25 | level=(level is not None) or logging.INFO,
26 | )
27 |
28 |
29 | def str_trim(o, max_char=80):
30 | """Return the string representation of an object, trimmed to keep up to `max_char` characters."""
31 | as_str = force_text(o)
32 | if len(as_str) > max_char:
33 | return "%s...(%i more)" % (as_str[:max_char], len(as_str) - max_char)
34 | else:
35 | return as_str
36 |
--------------------------------------------------------------------------------
/wolframclient/utils/packedarray.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import numpy as np
4 |
5 |
6 | class PackedArray(np.ndarray):
7 | """Wrapper class on top of NymPy ndarray used to preserve packed arrays when round-tripping them."""
8 |
--------------------------------------------------------------------------------
/wolframclient/utils/require.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from functools import wraps
4 |
5 | from wolframclient.utils.api import pip
6 |
7 |
8 | def installed_modules():
9 | return {i.key: i.version for i in pip.get_installed_distributions()}
10 |
11 |
12 | def missing_requirements(*modules):
13 |
14 | distributions = installed_modules()
15 |
16 | for module in modules:
17 | version = None
18 | if isinstance(module, (tuple, list)):
19 | module, version = module
20 |
21 | if module not in distributions or version and distributions[module] != version:
22 | yield version and "{}=={}".format(module, version) or module
23 |
24 |
25 | def require_module(*modules):
26 |
27 | commands = list(missing_requirements(*modules))
28 |
29 | if commands:
30 |
31 | print("Update in progress: pip install %s --user" % " ".join(commands))
32 |
33 | if pip.running_under_virtualenv():
34 | pip.main(["install", *commands])
35 | else:
36 | pip.main(["install", "--user", *commands])
37 |
38 |
39 | def require(*modules):
40 | def outer(func):
41 | @wraps(func)
42 | def inner(*args, **kw):
43 | require_module(*modules)
44 | return func(*args, **kw)
45 |
46 | return inner
47 |
48 | return outer
49 |
--------------------------------------------------------------------------------
/wolframclient/utils/six.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import itertools
4 | import platform
5 | import sys
6 | import types
7 |
8 | # stripped version of SIX
9 |
10 | PY2 = sys.version_info[0] == 2
11 | PY3 = sys.version_info[0] == 3
12 | PY_35 = sys.version_info >= (3, 5)
13 | PY_36 = sys.version_info >= (3, 6)
14 | PY_37 = sys.version_info >= (3, 7)
15 | PY_38 = sys.version_info >= (3, 8)
16 |
17 | WINDOWS = platform.system() == "Windows"
18 | LINUX = platform.system() == "Linux"
19 | MACOS = platform.system() == "Darwin"
20 |
21 | JYTHON = sys.platform.startswith("java")
22 |
23 | if PY3:
24 | string_types = (str,)
25 | integer_types = (int,)
26 | class_types = (type,)
27 | text_type = str
28 | binary_type = bytes
29 | none_type = type(None)
30 |
31 | import io
32 |
33 | StringIO = io.StringIO
34 | BytesIO = io.BytesIO
35 |
36 | buffer_types = (bytes, bytearray, memoryview)
37 |
38 | else:
39 | string_types = (basestring,)
40 | integer_types = (int, long)
41 | class_types = (type, types.ClassType)
42 | text_type = unicode
43 | binary_type = str
44 | none_type = types.NoneType
45 |
46 | import StringIO
47 |
48 | StringIO = BytesIO = StringIO.StringIO
49 |
50 | buffer_types = (bytearray, memoryview, buffer)
51 |
52 | iterable_types = [
53 | list,
54 | tuple,
55 | set,
56 | frozenset,
57 | types.GeneratorType,
58 | itertools.chain,
59 | itertools.groupby,
60 | ]
61 |
62 |
63 | if not PY2:
64 | iterable_types.extend((map, range))
65 |
--------------------------------------------------------------------------------
/wolframclient/utils/tests.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import unittest
4 |
5 | from wolframclient.utils.importutils import module_path
6 |
7 |
8 | class TestCase(unittest.TestCase):
9 | pass
10 |
11 |
12 | def path_to_file_in_data_dir(*args):
13 | return module_path("wolframclient", "tests", "data", *args)
14 |
--------------------------------------------------------------------------------
/wolframclient/utils/url.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | from wolframclient.utils import six
4 |
5 |
6 | def url_join(*fragments):
7 | """Join fragments of a URL, dealing with slashes."""
8 | if len(fragments) == 0:
9 | return ""
10 | buff = []
11 | for fragment in fragments:
12 | stripped = fragment.strip("/")
13 | if len(stripped) > 0:
14 | buff.append(stripped)
15 | buff.append("/")
16 |
17 | last = fragments[-1]
18 | # add a trailing '/' if present.
19 | if len(last) > 0 and last[-1] != "/":
20 | buff.pop()
21 | return "".join(buff)
22 |
23 |
24 | def evaluation_api_url(server):
25 | return url_join(server.cloudbase, "evaluations?_responseform=wxf")
26 |
27 |
28 | def user_api_url(server, api):
29 | """Build an API URL from a user name and an API id."""
30 | if isinstance(api, (list, tuple)):
31 | if len(api) == 2:
32 | return url_join(server.cloudbase, "objects", api[0], api[1])
33 | else:
34 | raise ValueError(
35 | "Target api specified as a tuple must have two elements: the user name, the API name."
36 | )
37 | elif isinstance(api, six.string_types):
38 | return api
39 | else:
40 | raise ValueError("Invalid API description. Expecting string or tuple.")
41 |
--------------------------------------------------------------------------------