Skip to content

Commit 746dd13

Browse files
committed
IGNITE-14705 Fix handling collections with binary objects - Fixes #37.
1 parent 9f72781 commit 746dd13

File tree

17 files changed

+260
-174
lines changed

17 files changed

+260
-174
lines changed

pyignite/aio_client.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from .exceptions import BinaryTypeError, CacheError, ReconnectError, connection_errors
3232
from .queries.query import CacheInfo
3333
from .stream import AioBinaryStream, READ_BACKWARD
34-
from .utils import cache_id, entity_id, status_to_exception, is_wrapped
34+
from .utils import cache_id, entity_id, status_to_exception
3535

3636

3737
__all__ = ['AioClient']
@@ -269,11 +269,24 @@ async def unwrap_binary(self, value: Any) -> Any:
269269
:return: the result of the Binary Object unwrapping with all other data
270270
left intact.
271271
"""
272-
if is_wrapped(value):
273-
blob, offset = value
274-
with AioBinaryStream(self, blob) as stream:
275-
data_class = await BinaryObject.parse_async(stream)
276-
return await BinaryObject.to_python_async(stream.read_ctype(data_class, direction=READ_BACKWARD), self)
272+
if isinstance(value, tuple) and len(value) == 2:
273+
if type(value[0]) is bytes and type(value[1]) is int:
274+
blob, offset = value
275+
with AioBinaryStream(self, blob) as stream:
276+
data_class = await BinaryObject.parse_async(stream)
277+
return await BinaryObject.to_python_async(stream.read_ctype(data_class, direction=READ_BACKWARD),
278+
client=self)
279+
280+
if isinstance(value[0], int):
281+
col_type, collection = value
282+
if isinstance(collection, list):
283+
coros = [self.unwrap_binary(v) for v in collection]
284+
return col_type, await asyncio.gather(*coros)
285+
286+
if isinstance(collection, dict):
287+
coros = [asyncio.gather(self.unwrap_binary(k), self.unwrap_binary(v))
288+
for k, v in collection.items()]
289+
return col_type, dict(await asyncio.gather(*coros))
277290
return value
278291

279292
@status_to_exception(CacheError)
@@ -351,7 +364,7 @@ async def get_best_node(
351364

352365
key, key_hint = self._get_affinity_key(c_id, key, key_hint)
353366

354-
hashcode = await key_hint.hashcode_async(key, self)
367+
hashcode = await key_hint.hashcode_async(key, client=self)
355368

356369
best_node = self._get_node_by_hashcode(c_id, hashcode, parts)
357370
if best_node:

pyignite/client.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
from .queries.query import CacheInfo
6262
from .stream import BinaryStream, READ_BACKWARD
6363
from .utils import (
64-
cache_id, capitalize, entity_id, schema_id, process_delimiter, status_to_exception, is_iterable, is_wrapped,
64+
cache_id, capitalize, entity_id, schema_id, process_delimiter, status_to_exception, is_iterable,
6565
get_field_by_id, unsigned
6666
)
6767
from .binary import GenericObjectMeta
@@ -539,17 +539,26 @@ def query_binary_type(self, binary_type: Union[int, str], schema: Union[int, dic
539539

540540
def unwrap_binary(self, value: Any) -> Any:
541541
"""
542-
Detects and recursively unwraps Binary Object.
542+
Detects and recursively unwraps Binary Object or collections of BinaryObject.
543543
544-
:param value: anything that could be a Binary Object,
544+
:param value: anything that could be a Binary Object or collection of BinaryObject,
545545
:return: the result of the Binary Object unwrapping with all other data
546546
left intact.
547547
"""
548-
if is_wrapped(value):
549-
blob, offset = value
550-
with BinaryStream(self, blob) as stream:
551-
data_class = BinaryObject.parse(stream)
552-
return BinaryObject.to_python(stream.read_ctype(data_class, direction=READ_BACKWARD), self)
548+
if isinstance(value, tuple) and len(value) == 2:
549+
if type(value[0]) is bytes and type(value[1]) is int:
550+
blob, offset = value
551+
with BinaryStream(self, blob) as stream:
552+
data_class = BinaryObject.parse(stream)
553+
return BinaryObject.to_python(stream.read_ctype(data_class, direction=READ_BACKWARD), client=self)
554+
555+
if isinstance(value[0], int):
556+
col_type, collection = value
557+
if isinstance(collection, list):
558+
return col_type, [self.unwrap_binary(v) for v in collection]
559+
560+
if isinstance(collection, dict):
561+
return col_type, {self.unwrap_binary(k): self.unwrap_binary(v) for k, v in collection.items()}
553562
return value
554563

555564
@status_to_exception(CacheError)
@@ -619,7 +628,7 @@ def get_best_node(
619628
return conn
620629

621630
key, key_hint = self._get_affinity_key(c_id, key, key_hint)
622-
hashcode = key_hint.hashcode(key, self)
631+
hashcode = key_hint.hashcode(key, client=self)
623632

624633
best_node = self._get_node_by_hashcode(c_id, hashcode, parts)
625634
if best_node:

pyignite/datatypes/base.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ class IgniteDataType(metaclass=IgniteDataTypeMeta):
4848
classes, both object and payload varieties.
4949
"""
5050
@classmethod
51-
async def hashcode_async(cls, value, *args, **kwargs):
52-
return cls.hashcode(value, *args, **kwargs)
51+
async def hashcode_async(cls, value, **kwargs):
52+
return cls.hashcode(value, **kwargs)
5353

5454
@classmethod
55-
def hashcode(cls, value, *args, **kwargs):
55+
def hashcode(cls, value, **kwargs):
5656
return 0
5757

5858
@classmethod
@@ -72,9 +72,9 @@ async def from_python_async(cls, stream, value, **kwargs):
7272
cls.from_python(stream, value, **kwargs)
7373

7474
@classmethod
75-
def to_python(cls, ctypes_object, *args, **kwargs):
75+
def to_python(cls, ctypes_object, **kwargs):
7676
raise NotImplementedError
7777

7878
@classmethod
79-
async def to_python_async(cls, ctypes_object, *args, **kwargs):
80-
return cls.to_python(ctypes_object, *args, **kwargs)
79+
async def to_python_async(cls, ctypes_object, **kwargs):
80+
return cls.to_python(ctypes_object, **kwargs)

pyignite/datatypes/cache_properties.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,12 @@ async def parse_async(cls, stream):
117117
return cls.parse(stream)
118118

119119
@classmethod
120-
def to_python(cls, ctypes_object, *args, **kwargs):
121-
return cls.prop_data_class.to_python(ctypes_object.data, *args, **kwargs)
120+
def to_python(cls, ctypes_object, **kwargs):
121+
return cls.prop_data_class.to_python(ctypes_object.data, **kwargs)
122122

123123
@classmethod
124-
async def to_python_async(cls, ctypes_object, *args, **kwargs):
125-
return cls.to_python(ctypes_object, *args, **kwargs)
124+
async def to_python_async(cls, ctypes_object, **kwargs):
125+
return cls.to_python(ctypes_object, **kwargs)
126126

127127
@classmethod
128128
def from_python(cls, stream, value):
@@ -302,6 +302,6 @@ def from_python(cls, stream, value):
302302
)
303303

304304
@classmethod
305-
def to_python(cls, ctypes_object, *args, **kwargs):
305+
def to_python(cls, ctypes_object, **kwargs):
306306
prop_data_class = prop_map(ctypes_object.prop_code)
307-
return prop_data_class.to_python(ctypes_object.data, *args, **kwargs)
307+
return prop_data_class.to_python(ctypes_object.data, **kwargs)

pyignite/datatypes/complex.py

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -90,22 +90,21 @@ def __build_final_class(cls, fields):
9090
)
9191

9292
@classmethod
93-
def to_python_not_null(cls, ctypes_object, *args, **kwargs):
93+
def to_python_not_null(cls, ctypes_object, **kwargs):
9494
result = []
9595
for i in range(ctypes_object.length):
9696
result.append(
9797
AnyDataObject.to_python(
98-
getattr(ctypes_object, f'element_{i}'),
99-
*args, **kwargs
98+
getattr(ctypes_object, f'element_{i}'), **kwargs
10099
)
101100
)
102101
return ctypes_object.type_id, result
103102

104103
@classmethod
105-
async def to_python_not_null_async(cls, ctypes_object, *args, **kwargs):
104+
async def to_python_not_null_async(cls, ctypes_object, **kwargs):
106105
result = [
107106
await AnyDataObject.to_python_async(
108-
getattr(ctypes_object, f'element_{i}'), *args, **kwargs
107+
getattr(ctypes_object, f'element_{i}'), **kwargs
109108
)
110109
for i in range(ctypes_object.length)]
111110
return ctypes_object.type_id, result
@@ -223,8 +222,6 @@ class CollectionObject(Nullable):
223222
_type_id = TYPE_COL
224223
_header_class = None
225224
type_code = TC_COLLECTION
226-
pythonic = list
227-
default = []
228225

229226
@classmethod
230227
def parse_not_null(cls, stream):
@@ -271,15 +268,15 @@ def __build_final_class(cls, fields):
271268
@classmethod
272269
def to_python_not_null(cls, ctypes_object, *args, **kwargs):
273270
result = [
274-
AnyDataObject.to_python(getattr(ctypes_object, f'element_{i}'), *args, **kwargs)
271+
AnyDataObject.to_python(getattr(ctypes_object, f'element_{i}'), **kwargs)
275272
for i in range(ctypes_object.length)
276273
]
277274
return ctypes_object.type, result
278275

279276
@classmethod
280277
async def to_python_not_null_async(cls, ctypes_object, *args, **kwargs):
281278
result_coro = [
282-
AnyDataObject.to_python_async(getattr(ctypes_object, f'element_{i}'), *args, **kwargs)
279+
AnyDataObject.to_python_async(getattr(ctypes_object, f'element_{i}'), **kwargs)
283280
for i in range(ctypes_object.length)
284281
]
285282

@@ -361,35 +358,27 @@ def __build_final_class(cls, fields):
361358
)
362359

363360
@classmethod
364-
def _to_python(cls, ctypes_object, *args, **kwargs):
361+
def _to_python(cls, ctypes_object, **kwargs):
365362
map_cls = cls.__get_map_class(ctypes_object)
366363

367364
result = map_cls()
368365
for i in range(0, ctypes_object.length << 1, 2):
369-
k = AnyDataObject.to_python(
370-
getattr(ctypes_object, f'element_{i}'),
371-
*args, **kwargs
372-
)
373-
v = AnyDataObject.to_python(
374-
getattr(ctypes_object, f'element_{i + 1}'),
375-
*args, **kwargs
376-
)
366+
k = AnyDataObject.to_python(getattr(ctypes_object, f'element_{i}'), **kwargs)
367+
v = AnyDataObject.to_python(getattr(ctypes_object, f'element_{i + 1}'), **kwargs)
377368
result[k] = v
378369
return result
379370

380371
@classmethod
381-
async def _to_python_async(cls, ctypes_object, *args, **kwargs):
372+
async def _to_python_async(cls, ctypes_object, **kwargs):
382373
map_cls = cls.__get_map_class(ctypes_object)
383374

384375
kv_pairs_coro = [
385376
asyncio.gather(
386377
AnyDataObject.to_python_async(
387-
getattr(ctypes_object, f'element_{i}'),
388-
*args, **kwargs
378+
getattr(ctypes_object, f'element_{i}'), **kwargs
389379
),
390380
AnyDataObject.to_python_async(
391-
getattr(ctypes_object, f'element_{i + 1}'),
392-
*args, **kwargs
381+
getattr(ctypes_object, f'element_{i + 1}'), **kwargs
393382
)
394383
) for i in range(0, ctypes_object.length << 1, 2)
395384
]
@@ -449,12 +438,12 @@ def _parse_header(cls, stream):
449438
return [('length', ctypes.c_int)], length
450439

451440
@classmethod
452-
def to_python(cls, ctypes_object, *args, **kwargs):
453-
return cls._to_python(ctypes_object, *args, **kwargs)
441+
def to_python(cls, ctypes_object, **kwargs):
442+
return cls._to_python(ctypes_object, **kwargs)
454443

455444
@classmethod
456-
async def to_python_async(cls, ctypes_object, *args, **kwargs):
457-
return await cls._to_python_async(ctypes_object, *args, **kwargs)
445+
async def to_python_async(cls, ctypes_object, **kwargs):
446+
return await cls._to_python_async(ctypes_object, **kwargs)
458447

459448
@classmethod
460449
def from_python(cls, stream, value, type_id=None):
@@ -484,8 +473,6 @@ class MapObject(Nullable, _MapBase):
484473
_type_name = NAME_MAP
485474
_type_id = TYPE_MAP
486475
type_code = TC_MAP
487-
pythonic = dict
488-
default = {}
489476

490477
@classmethod
491478
def parse_not_null(cls, stream):
@@ -507,12 +494,12 @@ def _parse_header(cls, stream):
507494
return fields, length
508495

509496
@classmethod
510-
def to_python_not_null(cls, ctypes_object, *args, **kwargs):
511-
return ctypes_object.type, cls._to_python(ctypes_object, *args, **kwargs)
497+
def to_python_not_null(cls, ctypes_object, **kwargs):
498+
return ctypes_object.type, cls._to_python(ctypes_object, **kwargs)
512499

513500
@classmethod
514-
async def to_python_not_null_async(cls, ctypes_object, *args, **kwargs):
515-
return ctypes_object.type, await cls._to_python_async(ctypes_object, *args, **kwargs)
501+
async def to_python_not_null_async(cls, ctypes_object, **kwargs):
502+
return ctypes_object.type, await cls._to_python_async(ctypes_object, **kwargs)
516503

517504
@classmethod
518505
def from_python_not_null(cls, stream, value, **kwargs):
@@ -557,7 +544,7 @@ class BinaryObject(Nullable):
557544
COMPACT_FOOTER = 0x0020
558545

559546
@classmethod
560-
def hashcode(cls, value: object, client: Optional['Client']) -> int:
547+
def hashcode(cls, value: object, client: Optional['Client'] = None) -> int:
561548
# binary objects's hashcode implementation is special in the sense
562549
# that you need to fully serialize the object to calculate
563550
# its hashcode
@@ -568,7 +555,7 @@ def hashcode(cls, value: object, client: Optional['Client']) -> int:
568555
return value._hashcode
569556

570557
@classmethod
571-
async def hashcode_async(cls, value: object, client: Optional['AioClient']) -> int:
558+
async def hashcode_async(cls, value: object, client: Optional['AioClient'] = None) -> int:
572559
if not value._hashcode and client:
573560
with AioBinaryStream(client) as stream:
574561
await value._from_python_async(stream, save_to_buf=True)
@@ -680,7 +667,7 @@ def __build_final_class(cls, stream, header, header_class, object_fields, fields
680667
return final_class
681668

682669
@classmethod
683-
def to_python_not_null(cls, ctypes_object, client: 'Client' = None, *args, **kwargs):
670+
def to_python_not_null(cls, ctypes_object, client: 'Client' = None, **kwargs):
684671
type_id = ctypes_object.type_id
685672
if not client:
686673
raise ParseError(f'Can not query binary type {type_id}')
@@ -692,14 +679,13 @@ def to_python_not_null(cls, ctypes_object, client: 'Client' = None, *args, **kwa
692679
for field_name, field_type in data_class.schema.items():
693680
setattr(
694681
result, field_name, field_type.to_python(
695-
getattr(ctypes_object.object_fields, field_name),
696-
client, *args, **kwargs
682+
getattr(ctypes_object.object_fields, field_name), client=client, **kwargs
697683
)
698684
)
699685
return result
700686

701687
@classmethod
702-
async def to_python_not_null_async(cls, ctypes_object, client: 'AioClient' = None, *args, **kwargs):
688+
async def to_python_not_null_async(cls, ctypes_object, client: 'AioClient' = None, **kwargs):
703689
type_id = ctypes_object.type_id
704690
if not client:
705691
raise ParseError(f'Can not query binary type {type_id}')
@@ -711,7 +697,7 @@ async def to_python_not_null_async(cls, ctypes_object, client: 'AioClient' = Non
711697
field_values = await asyncio.gather(
712698
*[
713699
field_type.to_python_async(
714-
getattr(ctypes_object.object_fields, field_name), client, *args, **kwargs
700+
getattr(ctypes_object.object_fields, field_name), client=client, **kwargs
715701
)
716702
for field_name, field_type in data_class.schema.items()
717703
]

pyignite/datatypes/expiry_policy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,14 @@ async def parse_async(cls, stream):
8080
return cls.parse(stream)
8181

8282
@classmethod
83-
def to_python(cls, ctypes_object):
83+
def to_python(cls, ctypes_object, **kwargs):
8484
if ctypes_object == 0:
8585
return None
8686

8787
return ExpiryPolicy(create=ctypes_object.create, update=ctypes_object.update, access=ctypes_object.access)
8888

8989
@classmethod
90-
async def to_python_async(cls, ctypes_object):
90+
async def to_python_async(cls, ctypes_object, **kwargs):
9191
return cls.to_python(ctypes_object)
9292

9393
@classmethod

0 commit comments

Comments
 (0)