Skip to content

Commit d803afc

Browse files
authored
GH-47358: [Python] IPC and Flight options representation (#47461)
### Rationale for this change Please see #47358 ### What changes are included in this PR? Added `__repr__` dunder methods to `ipc.IpcWriteOptions`, `ipc.IpcReadOptions` and `flight.FlightCallOptions` ### Are these changes tested? Yes ### Are there any user-facing changes? Nicer representations of the classes mentioned above * GitHub Issue: #47358 Lead-authored-by: Bogdan Romenskii <[email protected]> Co-authored-by: Bogdan Romenskii <[email protected]> Signed-off-by: AlenkaF <[email protected]>
1 parent 0e7e70c commit d803afc

File tree

6 files changed

+216
-4
lines changed

6 files changed

+216
-4
lines changed

python/pyarrow/_flight.pyx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,37 @@ cdef class FlightCallOptions(_Weakrefable):
142142
return &((<FlightCallOptions> obj).options)
143143
raise TypeError(f"Expected a FlightCallOptions object, not '{type(obj)}'")
144144

145+
@property
146+
def timeout(self):
147+
"""Get timeout for the call (in seconds)
148+
"""
149+
return self.options.timeout.count()
150+
151+
@property
152+
def headers(self):
153+
"""Get list of headers (key, value tuples) for client's context
154+
"""
155+
return self.options.headers
156+
157+
@property
158+
def read_options(self):
159+
"""Get serialization options for reading IPC format
160+
"""
161+
return wrap_ipc_read_options(self.options.read_options)
162+
163+
@property
164+
def write_options(self):
165+
"""Get IPC write options
166+
"""
167+
return wrap_ipc_write_options(self.options.write_options)
168+
169+
def __repr__(self):
170+
return (f"<pyarrow.flight.FlightCallOptions "
171+
f"timeout={self.timeout} "
172+
f"headers={self.headers}\n"
173+
f" read_options={self.read_options}\n"
174+
f" write_options={self.write_options}\n>")
175+
145176

146177
_CertKeyPair = collections.namedtuple('_CertKeyPair', ['cert', 'key'])
147178

python/pyarrow/includes/libarrow_flight.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ cdef extern from "arrow/flight/api.h" namespace "arrow" nogil:
273273

274274
cdef cppclass CTimeoutDuration" arrow::flight::TimeoutDuration":
275275
CTimeoutDuration(double)
276+
double count()
276277

277278
cdef cppclass CFlightCallOptions" arrow::flight::FlightCallOptions":
278279
CFlightCallOptions()

python/pyarrow/ipc.pxi

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,34 @@ cdef class IpcReadOptions(_Weakrefable):
197197
def included_fields(self, list value not None):
198198
self.c_options.included_fields = value
199199

200+
def __repr__(self):
201+
alignment = Alignment(self.ensure_alignment).name
202+
203+
return (f"<pyarrow.ipc.IpcReadOptions "
204+
f"ensure_native_endian={self.ensure_native_endian} "
205+
f"ensure_alignment={alignment} "
206+
f"use_threads={self.use_threads} "
207+
f"included_fields={self.included_fields}>")
208+
209+
210+
cdef IpcReadOptions wrap_ipc_read_options(CIpcReadOptions c):
211+
"""Get Python's IpcReadOptions from C++'s IpcReadOptions
212+
"""
213+
214+
return IpcReadOptions(
215+
ensure_native_endian=c.ensure_native_endian,
216+
ensure_alignment=c.ensure_alignment,
217+
use_threads=c.use_threads,
218+
included_fields=c.included_fields,
219+
)
220+
221+
222+
cdef object _get_compression_from_codec(shared_ptr[CCodec] codec):
223+
if codec == nullptr:
224+
return None
225+
else:
226+
return frombytes(codec.get().name())
227+
200228

201229
cdef class IpcWriteOptions(_Weakrefable):
202230
"""
@@ -277,10 +305,7 @@ cdef class IpcWriteOptions(_Weakrefable):
277305

278306
@property
279307
def compression(self):
280-
if self.c_options.codec == nullptr:
281-
return None
282-
else:
283-
return frombytes(self.c_options.codec.get().name())
308+
return _get_compression_from_codec(self.c_options.codec)
284309

285310
@compression.setter
286311
def compression(self, value):
@@ -324,6 +349,36 @@ cdef class IpcWriteOptions(_Weakrefable):
324349
def unify_dictionaries(self, bint value):
325350
self.c_options.unify_dictionaries = value
326351

352+
def __repr__(self):
353+
compression_repr = f"compression=\"{self.compression}\" " \
354+
if self.compression is not None else ""
355+
356+
metadata_version = MetadataVersion(self.metadata_version).name
357+
358+
return (f"<pyarrow.ipc.IpcWriteOptions "
359+
f"allow_64bit={self.allow_64bit} "
360+
f"use_legacy_format={self.use_legacy_format} "
361+
f"metadata_version={metadata_version} "
362+
f"{compression_repr}"
363+
f"use_threads={self.use_threads} "
364+
f"emit_dictionary_deltas={self.emit_dictionary_deltas} "
365+
f"unify_dictionaries={self.unify_dictionaries}>")
366+
367+
368+
cdef IpcWriteOptions wrap_ipc_write_options(CIpcWriteOptions c):
369+
"""Get Python's IpcWriteOptions from C++'s IpcWriteOptions
370+
"""
371+
372+
return IpcWriteOptions(
373+
metadata_version=c.metadata_version,
374+
allow_64bit=c.allow_64bit,
375+
use_legacy_format=c.write_legacy_ipc_format,
376+
compression=_get_compression_from_codec(c.codec),
377+
use_threads=c.use_threads,
378+
emit_dictionary_deltas=c.emit_dictionary_deltas,
379+
unify_dictionaries=c.unify_dictionaries,
380+
)
381+
327382

328383
cdef class Message(_Weakrefable):
329384
"""

python/pyarrow/lib.pxd

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,17 @@ cdef class IpcWriteOptions(_Weakrefable):
4343
CIpcWriteOptions c_options
4444

4545

46+
cdef IpcWriteOptions wrap_ipc_write_options(CIpcWriteOptions c)
47+
48+
4649
cdef class IpcReadOptions(_Weakrefable):
4750
cdef:
4851
CIpcReadOptions c_options
4952

5053

54+
cdef IpcReadOptions wrap_ipc_read_options(CIpcReadOptions c)
55+
56+
5157
cdef _wrap_read_stats(CIpcReadStats c)
5258

5359

python/pyarrow/tests/test_flight.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,15 @@
4747
ServerAuthHandler, ClientAuthHandler,
4848
ServerMiddleware, ServerMiddlewareFactory,
4949
ClientMiddleware, ClientMiddlewareFactory,
50+
FlightCallOptions,
5051
)
5152
except ImportError:
5253
flight = None
5354
FlightClient, FlightServerBase = object, object
5455
ServerAuthHandler, ClientAuthHandler = object, object
5556
ServerMiddleware, ServerMiddlewareFactory = object, object
5657
ClientMiddleware, ClientMiddlewareFactory = object, object
58+
FlightCallOptions = object
5759

5860
# Marks all of the tests in this module
5961
# Ignore these with pytest ... -m 'not flight'
@@ -2618,3 +2620,41 @@ def do_exchange(self, context, descriptor, reader, writer):
26182620

26192621
assert received_table.equals(expected_table)
26202622
assert reader.stats == expected_stats[command]
2623+
2624+
2625+
@pytest.fixture
2626+
def call_options_args(request):
2627+
if request.param == "default":
2628+
return {
2629+
"timeout": 3,
2630+
"headers": None,
2631+
"write_options": None,
2632+
"read_options": None,
2633+
}
2634+
elif request.param == "all":
2635+
return {
2636+
"timeout": 7,
2637+
"headers": [(b"abc", b"def")],
2638+
"write_options": pa.ipc.IpcWriteOptions(compression="zstd"),
2639+
"read_options": pa.ipc.IpcReadOptions(
2640+
use_threads=False,
2641+
ensure_alignment=pa.ipc.Alignment.DataTypeSpecific,
2642+
),
2643+
}
2644+
else:
2645+
return {}
2646+
2647+
2648+
@pytest.mark.parametrize(
2649+
"call_options_args", ["default", "all"], indirect=True)
2650+
def test_call_options_repr(call_options_args):
2651+
# https://github.com/apache/arrow/issues/47358
2652+
call_options = FlightCallOptions(**call_options_args)
2653+
repr = call_options.__repr__()
2654+
2655+
for arg, val in call_options_args.items():
2656+
if val is None:
2657+
assert arg in repr
2658+
continue
2659+
2660+
assert f"{arg}={val}" in repr

python/pyarrow/tests/test_ipc.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,3 +1338,82 @@ def test_record_batch_file_writer_with_empty_metadata():
13381338
buffer = sink.getvalue()
13391339
with pa.ipc.open_file(buffer) as r:
13401340
assert r.metadata is None
1341+
1342+
1343+
def check_ipc_options_repr(options_obj, options_args):
1344+
options = options_obj(**options_args)
1345+
repr = options.__repr__()
1346+
1347+
for arg, val in options_args.items():
1348+
if val is None:
1349+
continue
1350+
1351+
value = val if not isinstance(val, str) else f"\"{val}\""
1352+
1353+
if arg == "ensure_alignment":
1354+
value = pa.ipc.Alignment(val).name
1355+
elif arg == "metadata_version":
1356+
value = pa.ipc.MetadataVersion(val).name
1357+
1358+
assert f"{arg}={value}" in repr
1359+
1360+
1361+
@pytest.fixture
1362+
def write_options_args(request):
1363+
if request.param == "default":
1364+
return {
1365+
"allow_64bit": False,
1366+
"use_legacy_format": False,
1367+
"metadata_version": pa.ipc.MetadataVersion.V5,
1368+
"compression": None,
1369+
"use_threads": True,
1370+
"emit_dictionary_deltas": False,
1371+
"unify_dictionaries": False,
1372+
}
1373+
elif request.param == "all":
1374+
return {
1375+
"allow_64bit": True,
1376+
"use_legacy_format": True,
1377+
"metadata_version": pa.ipc.MetadataVersion.V4,
1378+
"compression": "zstd",
1379+
"use_threads": False,
1380+
"emit_dictionary_deltas": True,
1381+
"unify_dictionaries": True,
1382+
}
1383+
else:
1384+
return {}
1385+
1386+
1387+
@pytest.mark.zstd
1388+
@pytest.mark.parametrize(
1389+
"write_options_args", ["default", "all"], indirect=True)
1390+
def test_write_options_repr(write_options_args):
1391+
# https://github.com/apache/arrow/issues/47358
1392+
check_ipc_options_repr(pa.ipc.IpcWriteOptions, write_options_args)
1393+
1394+
1395+
@pytest.fixture
1396+
def read_options_args(request):
1397+
if request.param == "default":
1398+
return {
1399+
"ensure_native_endian": True,
1400+
"ensure_alignment": pa.ipc.Alignment.Any,
1401+
"use_threads": True,
1402+
"included_fields": None,
1403+
}
1404+
elif request.param == "all":
1405+
return {
1406+
"ensure_native_endian": False,
1407+
"ensure_alignment": pa.ipc.Alignment.DataTypeSpecific,
1408+
"use_threads": False,
1409+
"included_fields": [1, 2, 3],
1410+
}
1411+
else:
1412+
return {}
1413+
1414+
1415+
@pytest.mark.parametrize(
1416+
"read_options_args", ["default", "all"], indirect=True)
1417+
def test_read_options_repr(read_options_args):
1418+
# https://github.com/apache/arrow/issues/47358
1419+
check_ipc_options_repr(pa.ipc.IpcReadOptions, read_options_args)

0 commit comments

Comments
 (0)