Skip to content

Commit e37a5d3

Browse files
author
hhsecond
committed
simplified APIs stabilization init
1 parent 3e63d8a commit e37a5d3

File tree

6 files changed

+80
-175
lines changed

6 files changed

+80
-175
lines changed

redisai/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from .version import __version__
22
from .client import Client
3-
from .tensor import Tensor, BlobTensor
3+
from .tensorize import Tensor
44
from .constants import DType, Device, Backend

redisai/client.py

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from redis import StrictRedis
2-
from typing import Union, Any, AnyStr, ByteString, Sequence, Type
2+
from typing import Union, Any, AnyStr, ByteString, Sequence
3+
from collections import namedtuple
4+
from .containers import Script, Model, Tensor
35

46
try:
57
import numpy as np
@@ -8,7 +10,7 @@
810

911
from .constants import Backend, Device, DType
1012
from .utils import str_or_strsequence, to_string
11-
from .tensor import Tensor, BlobTensor
13+
from . import tensorize
1214

1315

1416
class Client(StrictRedis):
@@ -47,11 +49,10 @@ def modelset(self,
4749

4850
def modelget(self, name: AnyStr) -> dict:
4951
rv = self.execute_command('AI.MODELGET', name)
50-
return {
51-
'backend': Backend(to_string(rv[0])),
52-
'device': Device(to_string(rv[1])),
53-
'data': rv[2]
54-
}
52+
return Model(
53+
rv[2],
54+
Device(to_string(rv[1])),
55+
Backend(to_string(rv[0])))
5556

5657
def modeldel(self, name: AnyStr) -> AnyStr:
5758
return self.execute_command('AI.MODELDEL', name)
@@ -68,71 +69,64 @@ def modelrun(self,
6869

6970
def tensorset(self,
7071
key: AnyStr,
71-
tensor: Union[Tensor, np.ndarray, list, tuple],
72+
tensor: Union[np.ndarray, list, tuple],
7273
shape: Union[Sequence[int], None] = None,
7374
dtype: Union[DType, None] = None) -> Any:
7475
"""
7576
Set the values of the tensor on the server using the provided Tensor object
7677
:param key: The name of the tensor
77-
:param tensor: a `Tensor` object
78-
:param shape: Shape of the tensor
79-
:param dtype: data type of the tensor. Required if input is a sequence of ints/floats
78+
:param tensor: a `np.ndarray` object or python list or tuple
79+
:param shape: Shape of the tensor. Required if `tensor` is list or tuple
80+
:param dtype: data type of the tensor. Required if `tensor` is list or tuple
8081
"""
81-
# TODO: tensorset will not accept BlobTensor or Tensor object in the future.
82-
# Keeping it in the current version for compatibility with the example repo
8382
if np and isinstance(tensor, np.ndarray):
84-
tensor = BlobTensor.from_numpy(tensor)
83+
tensor = tensorize.from_numpy(tensor)
8584
elif isinstance(tensor, (list, tuple)):
8685
if shape is None:
8786
shape = (len(tensor),)
88-
tensor = Tensor(dtype, shape, tensor)
89-
args = ['AI.TENSORSET', key, tensor.type.value]
90-
args += tensor.shape
91-
args += [tensor.ARGNAME]
92-
args += tensor.value
87+
tensor = tensorize.from_sequence(tensor, shape, dtype)
88+
args = ['AI.TENSORSET', key, tensor.dtype.value, *tensor.shape, tensor.argname, *tensor.value]
9389
return self.execute_command(*args)
9490

9591
def tensorget(self,
96-
key: AnyStr, as_type: Type[Tensor] = None,
97-
meta_only: bool = False) -> Union[Tensor, BlobTensor]:
92+
key: AnyStr, as_numpy: bool = True,
93+
meta_only: bool = False) -> Union[namedtuple, np.ndarray]:
9894
"""
9995
Retrieve the value of a tensor from the server. By default it returns the numpy array
10096
but it can be controlled using `as_type` argument and `meta_only` argument.
10197
:param key: the name of the tensor
102-
:param as_type: the resultant tensor type. Returns numpy array if None
98+
:param as_numpy: Should it return data as numpy.ndarray.
99+
Wraps with namedtuple if False. This flag also decides how to fetch the
100+
value from RedisAI server and could have performance implications
103101
:param meta_only: if true, then the value is not retrieved,
104102
only the shape and the type
105103
:return: an instance of as_type
106104
"""
107-
# TODO; We might remove Tensor & BlobTensor in the future and `tensorget` will return
108-
# python list or numpy arrays or a namedtuple
109105
if meta_only:
110106
argname = 'META'
111-
elif as_type is None:
112-
argname = BlobTensor.ARGNAME
107+
elif as_numpy is True:
108+
argname = 'BLOB'
113109
else:
114-
argname = as_type.ARGNAME
110+
argname = 'VALUES'
115111

116112
res = self.execute_command('AI.TENSORGET', key, argname)
117113
dtype, shape = to_string(res[0]), res[1]
118-
dt = DType.__members__[dtype.lower()]
119114
if meta_only:
120-
return Tensor(dt, shape, [])
121-
elif as_type is None:
122-
return BlobTensor.from_resp(dt, shape, res[2]).to_numpy()
115+
return tensorize.to_sequence([], shape, dtype)
116+
if as_numpy is True:
117+
# TODO: excpetion handling
118+
return tensorize.to_numpy(res[2], shape, dtype)
123119
else:
124-
return as_type.from_resp(dt, shape, res[2])
120+
return tensorize.to_sequence(res[2], shape, dtype)
125121

126122
def scriptset(self, name: AnyStr, device: Device, script: AnyStr) -> AnyStr:
127123
return self.execute_command('AI.SCRIPTSET', name, device.value, script)
128124

129125
def scriptget(self, name: AnyStr) -> dict:
130126
r = self.execute_command('AI.SCRIPTGET', name)
131-
device = Device(to_string(r[0]))
132-
return {
133-
'device': device,
134-
'script': to_string(r[1])
135-
}
127+
return Script(
128+
to_string(r[1]),
129+
Device(to_string(r[0])))
136130

137131
def scriptdel(self, name):
138132
return self.execute_command('AI.SCRIPTDEL', name)

redisai/tensorize.py

Lines changed: 23 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
from typing import Union, ByteString, Sequence
2-
from collections import namedtuple
3-
import warnings
42
from .utils import convert_to_num
53
from .constants import DType
4+
from .containers import Tensor
65
try:
76
import numpy as np
87
except (ImportError, ModuleNotFoundError):
98
np = None
109

11-
12-
Tensor = namedtuple('Tensor', field_names=['value', 'shape', 'dtype', 'argname'])
10+
# TODO: verify the values for None or invalid values
11+
# TODO: type annotations
1312

1413

1514
def from_numpy(tensor):
@@ -19,118 +18,27 @@ def from_numpy(tensor):
1918
return Tensor(blob, shape, dtype, 'BLOB')
2019

2120

21+
def from_sequence(tensor, shape, dtype):
22+
return Tensor(tensor, shape, dtype, 'VALUES')
2223

23-
# Not necessary
24-
# =======================================================
25-
26-
27-
class TensorOld(object):
28-
ARGNAME = 'VALUES'
29-
30-
def __init__(self,
31-
dtype: DType,
32-
shape: Sequence[int],
33-
value):
34-
warnings.warn("Tensor APIs are depricated and "
35-
"will be removed from the future release.", UserWarning)
36-
"""
37-
Declare a tensor suitable for passing to tensorset
38-
:param dtype: The type the values should be stored as.
39-
This can be one of Tensor.FLOAT, tensor.DOUBLE, etc.
40-
:param shape: An array describing the shape of the tensor. For an
41-
image 250x250 with three channels, this would be [250, 250, 3]
42-
:param value: The value for the tensor. Can be an array.
43-
The contents must coordinate with the shape, meaning that the
44-
overall length needs to be the product of all figures in the
45-
shape. There is no verification to ensure that each dimension
46-
is correct. Your application must ensure that the ordering
47-
is always consistent.
48-
"""
49-
self.type = dtype
50-
self.shape = list(shape)
51-
self.value = value
52-
if not isinstance(value, (list, tuple)):
53-
self.value = [value]
54-
55-
def __repr__(self):
56-
return '<{c.__class__.__name__}(shape={s} type={t}) at 0x{id:x}>'.format(
57-
c=self,
58-
s=self.shape,
59-
t=self.type,
60-
id=id(self))
61-
62-
@classmethod
63-
def from_resp(cls, dtype: DType, shape: Sequence[int], value) -> 'Tensor':
64-
convert_to_num(dtype, value)
65-
return cls(dtype, shape, value)
66-
67-
@classmethod
68-
def scalar(cls, dtype: DType, *items) -> 'Tensor':
69-
"""
70-
Create a tensor from a list of numbers
71-
:param dtype: Type to use for storage
72-
:param items: One or more items
73-
:return: Tensor
74-
"""
75-
return cls(dtype, [len(items)], items)
76-
77-
78-
class BlobTensorOld(Tensor):
79-
ARGNAME = 'BLOB'
80-
81-
def __init__(self,
82-
dtype: DType,
83-
shape: Sequence[int],
84-
*blobs: Union['BlobTensor', ByteString]
85-
):
86-
"""
87-
Create a tensor from a binary blob
88-
:param dtype: The datatype, one of Tensor.FLOAT, Tensor.DOUBLE, etc.
89-
:param shape: An array
90-
:param blobs: One or more blobs to assign to the tensor.
91-
"""
92-
if len(blobs) > 1:
93-
blobarr = bytearray()
94-
for b in blobs:
95-
if isinstance(b, BlobTensor):
96-
b = b.value[0]
97-
blobarr += b
98-
size = len(blobs)
99-
ret_blobs = bytes(blobarr)
100-
shape = [size] + list(shape)
101-
else:
102-
ret_blobs = bytes(blobs[0])
103-
104-
super(BlobTensor, self).__init__(dtype, shape, ret_blobs)
105-
106-
@classmethod
107-
def from_numpy(cls, *nparrs) -> 'BlobTensor':
108-
blobs = []
109-
for arr in nparrs:
110-
blobs.append(arr.data)
111-
dt = DType.__members__[str(nparrs[0].dtype)]
112-
return cls(dt, nparrs[0].shape, *blobs)
113-
114-
@property
115-
def blob(self):
116-
return self.value[0]
11724

118-
def to_numpy(self) -> np.array:
119-
a = np.frombuffer(self.value[0], dtype=self._to_numpy_type(self.type))
120-
return a.reshape(self.shape)
25+
def to_numpy(value, shape, dtype):
26+
# tOdo exception
27+
dtype = DType.__members__[dtype.lower()].value
28+
mm = {
29+
'FLOAT': 'float32',
30+
'DOUBLE': 'float64'
31+
}
32+
if dtype in mm:
33+
dtype = mm[dtype]
34+
else:
35+
dtype = dtype.lower()
36+
a = np.frombuffer(value, dtype=dtype)
37+
return a.reshape(shape)
12138

122-
@staticmethod
123-
def _to_numpy_type(t):
124-
if isinstance(t, DType):
125-
t = t.value
126-
mm = {
127-
'FLOAT': 'float32',
128-
'DOUBLE': 'float64'
129-
}
130-
if t in mm:
131-
return mm[t]
132-
return t.lower()
13339

134-
@classmethod
135-
def from_resp(cls, dtype, shape, value) -> 'BlobTensor':
136-
return cls(dtype, shape, value)
40+
def to_sequence(value, shape, dtype):
41+
# TODO: what's the need for this? add test cases
42+
convert_to_num(dtype, value)
43+
dtype = DType.__members__[dtype.lower()]
44+
return Tensor(value, tuple(shape), dtype, 'VALUES')

redisai/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
# 1) we don't load dependencies by storing it in __init__.py
33
# 2) we can import it in setup.py for the same reason
44
# 3) we can import it into your module module
5-
__version__ = '0.4.1'
5+
__version__ = '0.5.0'

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
#!/usr/bin/env python
32
from setuptools import setup, find_packages
43

test/test.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,14 @@
11
from unittest import TestCase
22
import numpy as np
33
import os.path
4-
from redisai import Client, DType, Backend, Device, Tensor, BlobTensor
4+
from redisai import Client, DType, Backend, Device
55
from ml2rt import load_model
66
from redis.exceptions import ResponseError
77

88

99
MODEL_DIR = os.path.dirname(os.path.abspath(__file__)) + '/testdata'
1010

1111

12-
class TensorTestCase(TestCase):
13-
def testTensorShapes(self):
14-
t = Tensor(DType.float, [4], [1, 2, 3, 4])
15-
self.assertEqual([4], t.shape)
16-
t = BlobTensor.from_numpy(np.array([[1, 2, 3], [4, 5, 6]]))
17-
self.assertEqual([2, 3], t.shape)
18-
19-
2012
class ClientTestCase(TestCase):
2113
def setUp(self):
2214
super(ClientTestCase, self).setUp()
@@ -25,23 +17,35 @@ def setUp(self):
2517
def get_client(self):
2618
return Client()
2719

28-
def test_set_tensor(self):
20+
def test_set_non_numpy_tensor(self):
2921
con = self.get_client()
30-
con.tensorset('x', (2, 3), dtype=DType.float)
31-
values = con.tensorget('x', as_type=Tensor)
32-
self.assertEqual([2, 3], values.value)
22+
con.tensorset('x', (2, 3, 4, 5), dtype=DType.float)
23+
result = con.tensorget('x', as_numpy=False)
24+
self.assertEqual([2, 3, 4, 5], result.value)
25+
self.assertEqual((4,), result.shape)
26+
27+
con.tensorset('x', (2, 3, 4, 5), dtype=DType.int16, shape=(2, 2))
28+
result = con.tensorget('x', as_numpy=False)
29+
self.assertEqual([2, 3, 4, 5], result.value)
30+
self.assertEqual((2, 2), result.shape)
3331

34-
con.tensorset('x', Tensor.scalar(DType.int32, 2, 3))
35-
values = con.tensorget('x', as_type=Tensor).value
36-
self.assertEqual([2, 3], values)
37-
meta = con.tensorget('x', meta_only=True)
38-
self.assertTrue('<Tensor(shape=[2] type=DType.int32) at ' in repr(meta))
32+
with self.assertRaises(AttributeError):
33+
con.tensorset('x', (2, 3, 4), dtype=DType.int)
3934

40-
self.assertRaises(Exception, con.tensorset, 1)
41-
self.assertRaises(Exception, con.tensorset, 'x')
35+
with self.assertRaises(TypeError):
36+
con.tensorset('x')
37+
con.tensorset(1)
38+
39+
def test_meta(self):
40+
con = self.get_client()
41+
con.tensorset('x', (2, 3, 4, 5), dtype=DType.float)
42+
result = con.tensorget('x', meta_only=True)
43+
self.assertEqual([], result.value)
44+
self.assertEqual((4,), result.shape)
4245

4346
def test_numpy_tensor(self):
4447
con = self.get_client()
48+
4549
input_array = np.array([2, 3])
4650
con.tensorset('x', input_array)
4751
values1 = con.tensorget('x')

0 commit comments

Comments
 (0)