Skip to content

Commit 80f8d47

Browse files
committed
Merge pull request #234 in LCL/wolframclientforpython from feature/python-array to master
* commit '62c1511d0aaa1b00094b67acfd6b6a267d888b14': refactor Adding error message when PA type is invalid. refactor Add error message for invalid array type adding true false test adding packed array support renaming to numeric array using last testing shape working implementation of python array moving python array code refactor adding STRUCT_MAPPING constant adding python array class and test suite remove get
2 parents b98bcb8 + 62c1511 commit 80f8d47

10 files changed

Lines changed: 146 additions & 72 deletions

File tree

wolframclient/deserializers/wxf/wxfparser.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,27 +141,27 @@ def token_for_string(self, token):
141141

142142
def token_for_integer8(self, token):
143143
self.context.add_part()
144-
token.data = constants.StructInt8LE.unpack(self.reader.read(1))[0]
144+
token.data = constants.STRUCT_MAPPING.Integer8.unpack(self.reader.read(1))[0]
145145
return token
146146

147147
def token_for_integer16(self, token):
148148
self.context.add_part()
149-
token.data = constants.StructInt16LE.unpack(self.reader.read(2))[0]
149+
token.data = constants.STRUCT_MAPPING.Integer16.unpack(self.reader.read(2))[0]
150150
return token
151151

152152
def token_for_integer32(self, token):
153153
self.context.add_part()
154-
token.data = constants.StructInt32LE.unpack(self.reader.read(4))[0]
154+
token.data = constants.STRUCT_MAPPING.Integer32.unpack(self.reader.read(4))[0]
155155
return token
156156

157157
def token_for_integer64(self, token):
158158
self.context.add_part()
159-
token.data = constants.StructInt64LE.unpack(self.reader.read(8))[0]
159+
token.data = constants.STRUCT_MAPPING.Integer64.unpack(self.reader.read(8))[0]
160160
return token
161161

162162
def token_for_real64(self, token):
163163
self.context.add_part()
164-
token.data = constants.StructDouble.unpack(self.reader.read(8))[0]
164+
token.data = constants.STRUCT_MAPPING.Real64.unpack(self.reader.read(8))[0]
165165
return token
166166

167167
def token_for_function(self, token):

wolframclient/language/array.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from __future__ import absolute_import, print_function, unicode_literals
2+
3+
from wolframclient.exception import WolframLanguageException
4+
from wolframclient.serializers.wxfencoder import constants
5+
from wolframclient.utils.encoding import concatenate_bytes
6+
7+
try:
8+
from collections.abc import Sequence
9+
except ImportError:
10+
from collections import Sequence
11+
12+
13+
class NumericArray(Sequence):
14+
def __init__(self, array, type, shape=None):
15+
16+
self.array = array
17+
self.shape = shape or (len(array),)
18+
self.type = type
19+
self._valid_type_or_fail(type)
20+
self.struct = constants.STRUCT_MAPPING[type]
21+
22+
def _valid_type_or_fail(self, type):
23+
if type not in constants.STRUCT_MAPPING:
24+
raise WolframLanguageException(
25+
"Type %s is not one of the supported array types: %s."
26+
% (type, ", ".join(constants.STRUCT_MAPPING.keys()))
27+
)
28+
29+
def tobytes(self):
30+
return concatenate_bytes(self.struct.pack(el) for el in self.array)
31+
32+
def __getitem__(self, k):
33+
return self.array[k]
34+
35+
def __len__(self):
36+
return len(self.array)
37+
38+
39+
class PackedArray(NumericArray):
40+
def _valid_type_or_fail(self, type):
41+
if type not in constants.VALID_PACKED_ARRAY_LABEL_TYPES:
42+
raise WolframLanguageException(
43+
"Type %s is not one of the supported packed array types: %s."
44+
% (type, ", ".join(constants.VALID_PACKED_ARRAY_LABEL_TYPES_TUPLE))
45+
)

wolframclient/serializers/encoders/builtin.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import math
44

5+
from wolframclient.language.array import NumericArray, PackedArray
56
from wolframclient.language.expression import WLFunction, WLInputExpression, WLSymbol
67
from wolframclient.serializers.serializable import WLSerializable
78
from wolframclient.serializers.utils import safe_len
@@ -173,3 +174,13 @@ def encode_serializable(serializer, o):
173174
@encoder.dispatch(Association)
174175
def encode_association(serializer, o):
175176
return _to_key_value(serializer.serialize_association, serializer, o)
177+
178+
179+
@encoder.dispatch(NumericArray)
180+
def encode_array(serializer, o):
181+
return serializer.serialize_numeric_array(o.tobytes(), o.shape, o.type)
182+
183+
184+
@encoder.dispatch(PackedArray)
185+
def encode_array(serializer, o):
186+
return serializer.serialize_packed_array(o.tobytes(), o.shape, o.type)

wolframclient/serializers/wxfencoder/constants.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,29 @@ def _bytes(value):
9191
)
9292
)
9393

94-
StructInt8LE = struct.Struct(b"<b")
95-
StructUInt8LE = struct.Struct(b"<B")
96-
StructInt16LE = struct.Struct(b"<h")
97-
StructUInt16LE = struct.Struct(b"<H")
98-
StructInt32LE = struct.Struct(b"<i")
99-
StructUInt32LE = struct.Struct(b"<I")
100-
StructInt64LE = struct.Struct(b"<q")
101-
StructUInt64LE = struct.Struct(b"<Q")
102-
StructFloat = struct.Struct(b"<f")
103-
StructDouble = struct.Struct(b"<d")
94+
VALID_PACKED_ARRAY_LABEL_TYPES_TUPLE = (
95+
"Integer8",
96+
"Integer16",
97+
"Integer32",
98+
"Integer64",
99+
"Real32",
100+
"Real64",
101+
"ComplexReal32",
102+
"ComplexReal64",
103+
)
104+
VALID_PACKED_ARRAY_LABEL_TYPES = frozenset(VALID_PACKED_ARRAY_LABEL_TYPES_TUPLE)
105+
106+
STRUCT_MAPPING = Settings(
107+
Integer8=struct.Struct(b"<b"),
108+
UnsignedInteger8=struct.Struct(b"<B"),
109+
Integer16=struct.Struct(b"<h"),
110+
UnsignedInteger16=struct.Struct(b"<H"),
111+
Integer32=struct.Struct(b"<i"),
112+
UnsignedInteger32=struct.Struct(b"<I"),
113+
Integer64=struct.Struct(b"<q"),
114+
UnsignedInteger64=struct.Struct(b"<Q"),
115+
Real32=struct.Struct(b"<f"),
116+
Real64=struct.Struct(b"<d"),
117+
ComplexReal32=struct.Struct(b"<f"),
118+
ComplexReal64=struct.Struct(b"<d"),
119+
)

wolframclient/serializers/wxfencoder/utils.py

Lines changed: 16 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,13 @@
33
from wolframclient.exception import WolframLanguageException
44
from wolframclient.serializers.wxfencoder.constants import (
55
ARRAY_TYPES,
6+
STRUCT_MAPPING,
67
VALID_PACKED_ARRAY_TYPES,
78
WXF_CONSTANTS,
8-
StructDouble,
9-
StructFloat,
10-
StructInt8LE,
11-
StructInt16LE,
12-
StructInt32LE,
13-
StructInt64LE,
14-
StructUInt8LE,
15-
StructUInt16LE,
16-
StructUInt32LE,
17-
StructUInt64LE,
189
)
1910
from wolframclient.utils import six
2011
from wolframclient.utils.datastructures import Settings
12+
from wolframclient.utils.functional import last
2113

2214
if six.JYTHON:
2315
import jarray
@@ -71,21 +63,26 @@ def integer_size(value):
7163
raise ValueError("Value %i is not a machine-sized integer." % value)
7264

7365

74-
_packing = {1: StructInt8LE, 2: StructInt16LE, 4: StructInt32LE, 8: StructInt64LE}
66+
_packing = {
67+
1: STRUCT_MAPPING.Integer8,
68+
2: STRUCT_MAPPING.Integer16,
69+
4: STRUCT_MAPPING.Integer32,
70+
8: STRUCT_MAPPING.Integer64,
71+
}
7572

7673
if six.JYTHON:
7774

7875
def integer_to_bytes(value, int_size):
7976
buffer = jarray.zeros(8, "c")
80-
_packing.get(int_size).pack_into(buffer, 0, value)
77+
_packing[int_size].pack_into(buffer, 0, value)
8178
return buffer[:int_size].tostring()
8279

8380

8481
elif six.PY2:
8582

8683
def integer_to_bytes(value, int_size):
8784
buffer = bytearray(8)
88-
_packing.get(int_size).pack_into(buffer, 0, value)
85+
_packing[int_size].pack_into(buffer, 0, value)
8986
return buffer[:int_size]
9087

9188

@@ -97,17 +94,17 @@ def integer_to_bytes(value, int_size):
9794

9895
if six.JYTHON:
9996

100-
def float_to_bytes(value):
97+
def float_to_bytes(value, pack_into=STRUCT_MAPPING.Real64.pack_into):
10198
buffer = jarray.zeros(8, "c")
102-
StructDouble.pack_into(buffer, 0, value)
99+
pack_into(buffer, 0, value)
103100
return buffer.tostring()
104101

105102

106103
else:
107104

108-
def float_to_bytes(value):
105+
def float_to_bytes(value, pack_into=STRUCT_MAPPING.Real64.pack_into):
109106
buffer = bytearray(8)
110-
StructDouble.pack_into(buffer, 0, value)
107+
pack_into(buffer, 0, value)
111108
return buffer
112109

113110

@@ -146,20 +143,7 @@ def array_to_list(data, dimensions, wl_type):
146143

147144

148145
if hasattr(memoryview, "cast"):
149-
unpack_mapping = Settings(
150-
Integer8="b",
151-
UnsignedInteger8="B",
152-
Integer16="h",
153-
UnsignedInteger16="H",
154-
Integer32="i",
155-
UnsignedInteger32="I",
156-
Integer64="q",
157-
UnsignedInteger64="Q",
158-
Real32="f",
159-
Real64="d",
160-
ComplexReal32="f",
161-
ComplexReal64="d",
162-
)
146+
unpack_mapping = Settings((k, last(v.format)) for k, v in STRUCT_MAPPING.items())
163147

164148
def _to_complex(array, max_depth, curr_depth):
165149
# recursivelly traverse the array until the last (real) dimension is reached
@@ -188,20 +172,6 @@ def _array_to_list(data, shape, array_type):
188172

189173

190174
else:
191-
unpack_mapping = Settings(
192-
Integer8=StructInt8LE,
193-
UnsignedInteger8=StructUInt8LE,
194-
Integer16=StructInt16LE,
195-
UnsignedInteger16=StructUInt16LE,
196-
Integer32=StructInt32LE,
197-
UnsignedInteger32=StructUInt32LE,
198-
Integer64=StructInt64LE,
199-
UnsignedInteger64=StructUInt64LE,
200-
Real32=StructFloat,
201-
Real64=StructDouble,
202-
ComplexReal32=StructFloat,
203-
ComplexReal64=StructDouble,
204-
)
205175

206176
def _array_to_list(data, shape, array_type):
207177
value, _ = _build_array_from_bytes(data, 0, array_type, shape, 0)
@@ -216,7 +186,7 @@ def _build_array_from_bytes(data, offset, array_type, dimensions, current_dim):
216186
)
217187
new_array.append(new_elem)
218188
else:
219-
struct = unpack_mapping[array_type]
189+
struct = STRUCT_MAPPING[array_type]
220190
# complex values, need two reals for each.
221191
if array_type == "ComplexReal32" or array_type == "ComplexReal64":
222192
for i in range(dimensions[-1]):

wolframclient/tests/serializers/wl_serialization.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ def test_datetime(self):
9292

9393
def test_date(self):
9494

95-
self.compare(test_datetime().date(), b'DateObject[{2000, 1, 1}, "Day", "Gregorian", None]')
95+
self.compare(
96+
test_datetime().date(), b'DateObject[{2000, 1, 1}, "Day", "Gregorian", None]'
97+
)
9698

9799
def test_timedelta(self):
98100

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
)

wolframclient/utils/api.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
from wolframclient.utils.importutils import API
44

55
ast = API(
6-
Module = 'ast.Module',
7-
PyCF_ONLY_AST = 'ast.PyCF_ONLY_AST',
8-
Expr = 'ast.Expr',
9-
Expression = 'ast.Expression',
10-
FunctionDef = 'ast.FunctionDef',
11-
ClassDef = 'ast.ClassDef',
6+
Module="ast.Module",
7+
PyCF_ONLY_AST="ast.PyCF_ONLY_AST",
8+
Expr="ast.Expr",
9+
Expression="ast.Expression",
10+
FunctionDef="ast.FunctionDef",
11+
ClassDef="ast.ClassDef",
1212
)
1313

1414
pytz = API(

wolframclient/utils/externalevaluate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
# https://github.com/ipython/ipython/issues/11590
3434
# PY_38 requires type_ignores to be a list, other versions are not accepting a second argument
3535

36-
def Module(code, type_ignores = []):
36+
def Module(code, type_ignores=[]):
3737
return ast.Module(code, type_ignores)
3838

3939

wolframclient/utils/six.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from __future__ import absolute_import, print_function, unicode_literals
22

3-
import datetime
4-
import decimal
53
import itertools
64
import platform
75
import sys
@@ -61,4 +59,4 @@
6159
itertools.groupby,
6260
]
6361
if not PY2:
64-
iterable_types.extend((map, range))
62+
iterable_types.extend((map, range))

0 commit comments

Comments
 (0)