├── .coveragerc ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── PacletInfo.m ├── README.rst ├── docs ├── .gitignore ├── Makefile ├── build_docs.sh ├── conf.py ├── deploy.m ├── docpages │ ├── advanced_usages.rst │ ├── basic_usages.rst │ ├── install.rst │ ├── public_api.rst │ └── toc.rst ├── docutils.conf ├── examples │ ├── python │ │ ├── asynchronous1.py │ │ ├── asynchronous2.py │ │ ├── asynchronous3.py │ │ ├── asynchronous4.py │ │ ├── eigenvalues1.py │ │ ├── eigenvalues2.py │ │ ├── eigenvalues3.py │ │ ├── eigenvalues3_alternative.py │ │ ├── encoder1.py │ │ ├── encoder2.py │ │ ├── globalcontext.py │ │ ├── logging1.py │ │ └── logging2.py │ └── svg │ │ ├── barchart.svg │ │ ├── basicarraylistplot.svg │ │ ├── ev1.svg │ │ ├── ev2.svg │ │ ├── ev3.svg │ │ ├── listplotPrime25.svg │ │ ├── matrix.svg │ │ └── piechart.svg ├── index.rst ├── make.bat ├── requirements.txt ├── roles │ ├── __init__.py │ └── wl.py ├── templates │ ├── globaltoc.html │ ├── layout.html │ └── sidecopyright.html └── wri_theme │ ├── static │ ├── mma.scss │ ├── python-client-library-logo.png │ ├── wolf-python-arrowleft.png │ ├── wolf-python-arrowright.png │ ├── wolf-python-homepage.png │ ├── wolf-python-subpage.png │ ├── wri-index.css │ └── wri.css │ └── theme.conf ├── run.py ├── scripts └── re_build_WolframClient.xml ├── setup.cfg ├── setup.py └── wolframclient ├── __init__.py ├── __main__.py ├── about.py ├── cli ├── __init__.py ├── commands │ ├── __init__.py │ ├── benchmark.py │ ├── refactor.py │ ├── setup.py │ ├── start_externalevaluate.py │ └── test.py ├── dispatch.py └── utils.py ├── deserializers ├── __init__.py └── wxf │ ├── __init__.py │ ├── wxfconsumer.py │ └── wxfparser.py ├── evaluation ├── __init__.py ├── base.py ├── cloud │ ├── __init__.py │ ├── asynccloudsession.py │ ├── asyncoauth.py │ ├── base.py │ ├── cloudsession.py │ ├── oauth.py │ ├── request_adapter.py │ └── server.py ├── kernel │ ├── __init__.py │ ├── asyncsession.py │ ├── initkernel.m │ ├── kernelcontroller.py │ ├── localsession.py │ └── zmqsocket.py ├── pool.py └── result.py ├── exception.py ├── language ├── __init__.py ├── array.py ├── decorators.py ├── exceptions.py ├── expression.py ├── side_effects.py └── traceback.py ├── serializers ├── __init__.py ├── base.py ├── encoder.py ├── encoders │ ├── __init__.py │ ├── astropy.py │ ├── builtin.py │ ├── datetime.py │ ├── decimal.py │ ├── fractions.py │ ├── io.py │ ├── numpy.py │ ├── pandas.py │ ├── pil.py │ └── zoneinfo.py ├── serializable.py ├── utils.py ├── wl.py ├── wxf.py └── wxfencoder │ ├── __init__.py │ ├── constants.py │ ├── serializer.py │ ├── streaming.py │ ├── utils.py │ ├── wxfencoder.py │ ├── wxfexpr.py │ ├── wxfexprprovider.py │ └── wxfnumpyencoder.py ├── settings.py ├── tests ├── __init__.py ├── configure.py ├── core_functions.py ├── data │ ├── 10ct_32bit_128.tiff │ ├── 16_bit_binary_pgm.png │ ├── 32x2.png │ ├── 500x200.png │ ├── 5x2.png │ ├── allbytes.wl │ ├── allbytes.wxf │ ├── allchars.wxf │ ├── hopper.ppm │ ├── pal1wb.bmp │ ├── pil_sample_cmyk.jpg │ └── umbrellaRGBA.png ├── deserializers │ ├── __init__.py │ └── wxf_deserialize.py ├── evaluation │ ├── __init__.py │ ├── api.m │ ├── test_async_cloud.py │ ├── test_cloud.py │ ├── test_coroutine.py │ └── test_kernel.py ├── externalevaluate │ ├── __init__.py │ └── ev_loop.py ├── local_config_sample.json ├── log │ └── .gitignore ├── serializers │ ├── __init__.py │ ├── encoder.py │ ├── pandas.py │ ├── wl_export.py │ ├── wl_pil.py │ ├── wl_serialization.py │ ├── wxf_compress.py │ ├── wxf_encoder.py │ ├── wxf_numpy.py │ ├── wxf_pythonarray.py │ └── wxf_serialization.py └── test_utils.py └── utils ├── __init__.py ├── api.py ├── asyncio.py ├── datastructures.py ├── debug.py ├── decorators.py ├── dispatch.py ├── encoding.py ├── environment.py ├── externalevaluate.py ├── functional.py ├── importutils.py ├── json.py ├── lock.py ├── logger.py ├── packedarray.py ├── require.py ├── six.py ├── tests.py └── url.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch=True 3 | timid=True 4 | source=wolframclient 5 | omit = 6 | # client 7 | wolframclient/cli/* 8 | # unit tests 9 | wolframclient/tests/* 10 | # libraries 11 | /usr/* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | *$py.class 4 | /build 5 | /dist 6 | *.egg-info 7 | wolframclient/tests/local_config.json 8 | /htmlcov 9 | .coverage 10 | .vscode 11 | *.code-workspace 12 | /test-reports 13 | .DS_Store 14 | /env 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Wolfram® 2 | 3 | Thank you for taking the time to contribute to the [Wolfram Research](https://github.com/wolframresearch) repos on GitHub. 4 | 5 | ## Licensing of Contributions 6 | 7 | By contributing to Wolfram, you agree and affirm that: 8 | 9 | > Wolfram may release your contribution under the terms of the [MIT license](https://opensource.org/licenses/MIT); and 10 | 11 | > You have read and agreed to the [Developer Certificate of Origin](http://developercertificate.org/), version 1.1 or later. 12 | 13 | Please see [LICENSE](LICENSE) for licensing conditions pertaining 14 | to individual repositories. 15 | 16 | 17 | ## Bug reports 18 | 19 | ### Security Bugs 20 | 21 | Please **DO NOT** file a public issue regarding a security issue. 22 | Rather, send your report privately to security@wolfram.com. Security 23 | reports are appreciated and we will credit you for it. We do not offer 24 | a security bounty, but the forecast in your neighborhood will be cloudy 25 | with a chance of Wolfram schwag! 26 | 27 | ### General Bugs 28 | 29 | Please use the repository issues page to submit general bug issues. 30 | 31 | Please do not duplicate issues. 32 | 33 | Please do send a complete and well-written report to us. Note: **the 34 | thoroughness of your report will positively correlate to our willingness 35 | and ability to address it**. 36 | 37 | When reporting issues, always include: 38 | 39 | * Your version of *Mathematica*® or the Wolfram Language. 40 | * Your operating system. 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Wolfram Research Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE PacletInfo.m run.py 2 | recursive-include wolframclient *.m 3 | recursive-include wolframclient/tests/data * 4 | recursive-include wolframclient/tests *.py -------------------------------------------------------------------------------- /PacletInfo.m: -------------------------------------------------------------------------------- 1 | Paclet[ 2 | Name -> "WolframClientForPython", 3 | Version -> "1.1.10", 4 | MathematicaVersion -> "12.3+", 5 | Loading -> Automatic, 6 | Extensions -> {} 7 | ] -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://wolframresearch.github.io/WolframClientForPython/_static/python-client-library-logo.png 2 | :alt: Wolfram Logo 3 | 4 | 5 | ########################################## 6 | Wolfram Client library for Python 7 | ########################################## 8 | 9 | The Wolfram Client Library provides seamless Wolfram Language integration in Python. A list of features provided by the library: 10 | 11 | * evaluate arbitrary code on a local kernel, 12 | * evaluate arbitrary code on Wolfram cloud, public or private, 13 | * call deployed `APIFunction`, 14 | * build Python functions on top of Wolfram Language functions, 15 | * represent arbitrary Wolfram Language code as Python object, 16 | * serialize Python object to Wolfram Language string `InputForm`, 17 | * serialize Python object to `WXF`, 18 | * extend serialization to any arbitrary Python class, 19 | * parse `WXF` encoded expressions. 20 | 21 | ############# 22 | Links 23 | ############# 24 | 25 | * `Installation instructions `_ 26 | * `Documentation `_ 27 | * `Announcement Blog post `_ 28 | * `Changelog `_ 29 | * `Contributor guidelines `_ 30 | * `License `_ 31 | 32 | ######################### 33 | Project Information 34 | ######################### 35 | 36 | Licencing 37 | ============= 38 | 39 | This project is released under the MIT licence. 40 | 41 | Contributions 42 | ============= 43 | 44 | The library is maintained by Wolfram Research. The code is on Github. Pull requests and suggestions are always welcomed. -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | api/ 3 | _build/ -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | # SPHINXBUILD = sphinx-build 7 | SPHINXBUILD = python3 -msphinx 8 | SPHINXPROJ = WolframClientLibraryForPython 9 | SOURCEDIR = . 10 | BUILDDIR = _build 11 | 12 | # Put it first so that "make" without argument is like "make help". 13 | help: 14 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 15 | 16 | .PHONY: help Makefile 17 | 18 | # Catch-all target: route all unknown targets to Sphinx using the new 19 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 20 | %: Makefile 21 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/build_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #> Wolfram Python documentation build script. (UNIX only) 4 | #> This script builds the documentation static webpages from restructured files, 5 | #> using sphinx make. 6 | #> 7 | #> OPTIONS: 8 | #> 9 | #> -b path, --build=path 10 | #> set the output directory to path. It must be a valid directory. 11 | #> BUILDDIR is set to path if specified, otherwise the default 12 | #> directory _build is used. 13 | #> 14 | #> -a, --all 15 | #> rebuild from scratch. Call `make clean` and remove the api directory. 16 | #> Useful when the codebase has changed and some source files were removed. 17 | #> 18 | #> -h display this page. 19 | #> 20 | 21 | function help(){ 22 | less $0 | grep -e "^#>" | sed 's/^#> \(.*\)$/\1/g' 23 | exit $1 || 0 24 | } 25 | 26 | export LC_ALL="en_US.UTF-8" 27 | export LC_CTYPE="en_US.UTF-8" 28 | 29 | target='' 30 | 31 | while [ "$#" -gt 0 ]; do 32 | case "$1" in 33 | -a) all=1; shift 1;; 34 | -b) target="$2"; shift 2;; 35 | -h) help 0;; 36 | --all) all=1; shift 1;; 37 | --build=*) target="${1#*=}"; shift 1;; 38 | --help) help 0;; 39 | -*) echo "unknown option: $1"; exit 1;; 40 | *) echo "unexpected argument: $1"; exit 1;; 41 | esac 42 | done 43 | 44 | if [[ ! -z "${target}" ]]; then 45 | if [[ ! -d "${target}" ]]; then 46 | echo "Invalid build directory: ${target}" 47 | exit 1 48 | fi 49 | # normalize path 50 | target="`dirname \"${target}\"`/`basename \"${target}\"`" 51 | fi 52 | 53 | # clean up 54 | if [[ $all == 1 ]]; then 55 | [[ -r ./api ]] && rm -r ./api 56 | if [[ ! -z "${target}" ]]; then 57 | echo "Removing ${target}/html" 58 | rm -r "${target}/html" 59 | echo "Removing ${target}/*.js" 60 | rm "${target}/*.js" 61 | echo "Removing ${target}/*.html" 62 | rm "${target}/*.html" 63 | fi 64 | fi 65 | # static analysis 66 | sphinx-apidoc -o api ../wolframclient ../wolframclient/tests* 67 | 68 | # build html 69 | make html 70 | 71 | echo "Compile mma.scss to ./_build/html/_static/mma.css" 72 | sass ./wri_theme/static/mma.scss > "./_build/html/_static/mma.css" 73 | 74 | if [[ ! -z "${target}" ]]; then 75 | echo "Copying _build to target: ${target}" 76 | cp -r "./_build/html/." "${target}" 77 | fi -------------------------------------------------------------------------------- /docs/deploy.m: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | ClearAll[DeployDocumentation]; 4 | DeployDocumentation[root_String, target_String, setperm : True | False : False] := ( 5 | Parallelize@Scan[ 6 | With[{relpath = 7 | First@StringCases[#, root ~~ "/" ... ~~ relative__ :> relative, 8 | 1]}, 9 | PrintTemporary["Copying " <> relpath]; 10 | CopyFile[#, CloudObject[FileNameJoin[{target,relpath}]], 11 | OverwriteTarget -> True]; 12 | If[setperm, 13 | SetOptions[CloudObject[FileNameJoin[{target,relpath}]], 14 | Permissions -> "Public"] 15 | ]; 16 | ] 17 | &, 18 | FileNames[{"*.js", "*.html", "*.css", "*.png", "*.svg"}, root, Infinity] 19 | ]; 20 | If[setperm, 21 | SetOptions[CloudObject[target],Permissions -> "Public"] 22 | ]; 23 | CloudObject[FileNameJoin[{target,"index.html"}]] 24 | ); 25 | 26 | $doc = "/Users/dorianb/Work/Mathematica/Workspaces/WolframClientForPython/docs"; 27 | $root = FileNameJoin[{$doc, "_build","html"}] 28 | DeployDocumentation[$root, "lcl/python/doc/", True] 29 | 30 | 31 | $doc = "/Users/dorianb/Work/Matematica/Workspaces/wxfparser/doc"; 32 | DeployDocumentation[$doc,"wxf/java/doc/", True] 33 | 34 | 35 | SetPermissions[CloudObject["wxf/java/doc"],"Public"] 36 | -------------------------------------------------------------------------------- /docs/docpages/install.rst: -------------------------------------------------------------------------------- 1 | .. toctree:: 2 | :maxdepth: 4 3 | 4 | Installation and Prerequisites 5 | ****************************** 6 | 7 | The library source code is available in various repositories: 8 | 9 | * hosted on `PyPI `_ and available in `pip` 10 | * in a public repository on `GitHub `_ 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 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/examples/svg/piechart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 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 |
14 |
15 | {% if pagename != 'index' %} 16 | 36 | 62 | {% endif %} 63 |
64 |
65 | {% endblock %} 66 | 67 | {% block relbar2 %} 68 | {% endblock %} 69 | 70 | {% block footer %} 71 | {% endblock %} -------------------------------------------------------------------------------- /docs/templates/sidecopyright.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | © {{ copyright }} 4 |
5 |
-------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------