Skip to content

Commit c9d5a4f

Browse files
committed
Add tests
1 parent cc19d6e commit c9d5a4f

1 file changed

Lines changed: 161 additions & 0 deletions

File tree

tests/actions/export_entries/test_utils.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import csv
12
import json
23
from datetime import datetime
34

@@ -321,3 +322,163 @@ def test_write_table_rows_to_tabular_file_reads_ndjson(tmp_path):
321322
assert table.column_names == ['entry_id', 'upload_id', 'value']
322323
assert table['entry_id'].to_pylist() == ['one', 'two']
323324
assert table['value'].to_pylist() == ['first', 'second']
325+
326+
327+
def test_write_table_rows_to_tabular_file_builds_wide_multi_row_batches(
328+
tmp_path,
329+
monkeypatch,
330+
):
331+
rows_path = tmp_path / 'rows.ndjson'
332+
output_path = tmp_path / 'rows.parquet'
333+
quantity_defs = {f'value_{index:03}': Quantity(type=str) for index in range(64)}
334+
rows = [
335+
{
336+
'entry_id': f'entry_{index}',
337+
'upload_id': 'upload',
338+
f'value_{index:03}': f'content_{index}',
339+
}
340+
for index in range(5)
341+
]
342+
rows_path.write_text(
343+
''.join(f'{json.dumps(row, separators=(",", ":"))}\n' for row in rows),
344+
encoding='utf-8',
345+
)
346+
batch_sizes = []
347+
original_converter = utils._table_rows_to_arrow_batch
348+
349+
def capture_batch_size(batch_rows, *args, **kwargs):
350+
batch_sizes.append(len(batch_rows))
351+
return original_converter(batch_rows, *args, **kwargs)
352+
353+
monkeypatch.setattr(utils, '_table_rows_to_arrow_batch', capture_batch_size)
354+
count = utils.write_table_rows_to_tabular_file(
355+
rows_path,
356+
output_path,
357+
quantity_defs,
358+
max_buffer_bytes=1024 * 1024,
359+
max_buffer_rows=3,
360+
)
361+
362+
table = pq.read_table(output_path)
363+
parquet_file = pq.ParquetFile(output_path)
364+
expected_batch_sizes = [3, 2]
365+
assert count == len(rows)
366+
assert batch_sizes == expected_batch_sizes
367+
assert parquet_file.metadata.num_row_groups == len(expected_batch_sizes)
368+
assert table['entry_id'].to_pylist() == [row['entry_id'] for row in rows]
369+
370+
371+
def test_write_table_rows_to_tabular_file_flushes_on_input_bytes(
372+
tmp_path,
373+
monkeypatch,
374+
):
375+
rows_path = tmp_path / 'rows.ndjson'
376+
output_path = tmp_path / 'rows.parquet'
377+
rows = [
378+
{'entry_id': f'entry_{index}', 'upload_id': 'upload', 'value': 'content'}
379+
for index in range(3)
380+
]
381+
encoded_lines = [
382+
f'{json.dumps(row, separators=(",", ":"))}\n'.encode() for row in rows
383+
]
384+
rows_path.write_bytes(b''.join(encoded_lines))
385+
batch_sizes = []
386+
original_converter = utils._table_rows_to_arrow_batch
387+
388+
def capture_batch_size(batch_rows, *args, **kwargs):
389+
batch_sizes.append(len(batch_rows))
390+
return original_converter(batch_rows, *args, **kwargs)
391+
392+
monkeypatch.setattr(utils, '_table_rows_to_arrow_batch', capture_batch_size)
393+
394+
count = utils.write_table_rows_to_tabular_file(
395+
rows_path,
396+
output_path,
397+
{'value': Quantity(type=str)},
398+
max_buffer_bytes=len(encoded_lines[0]) + len(encoded_lines[1]),
399+
)
400+
401+
expected_batch_sizes = [2, 1]
402+
assert count == len(rows)
403+
assert batch_sizes == expected_batch_sizes
404+
assert pq.ParquetFile(output_path).metadata.num_row_groups == len(
405+
expected_batch_sizes
406+
)
407+
408+
409+
def test_write_table_rows_to_tabular_file_writes_oversized_row_immediately(
410+
tmp_path,
411+
monkeypatch,
412+
):
413+
rows_path = tmp_path / 'rows.ndjson'
414+
output_path = tmp_path / 'rows.parquet'
415+
rows = [
416+
{'entry_id': 'large', 'upload_id': 'upload', 'value': 'x' * 1000},
417+
{'entry_id': 'small', 'upload_id': 'upload', 'value': 'x'},
418+
]
419+
encoded_lines = [
420+
f'{json.dumps(row, separators=(",", ":"))}\n'.encode() for row in rows
421+
]
422+
rows_path.write_bytes(b''.join(encoded_lines))
423+
batch_sizes = []
424+
warnings = []
425+
original_converter = utils._table_rows_to_arrow_batch
426+
427+
def capture_batch_size(batch_rows, *args, **kwargs):
428+
batch_sizes.append(len(batch_rows))
429+
return original_converter(batch_rows, *args, **kwargs)
430+
431+
class CapturingLogger:
432+
def info(self, message):
433+
pass
434+
435+
def warning(self, message, **kwargs):
436+
warnings.append((message, kwargs))
437+
438+
monkeypatch.setattr(utils, '_table_rows_to_arrow_batch', capture_batch_size)
439+
440+
count = utils.write_table_rows_to_tabular_file(
441+
rows_path,
442+
output_path,
443+
{'value': Quantity(type=str)},
444+
max_buffer_bytes=len(encoded_lines[1]) + 1,
445+
logger=CapturingLogger(),
446+
)
447+
448+
assert count == len(rows)
449+
assert batch_sizes == [1, 1]
450+
assert len(warnings) == 1
451+
assert warnings[0][1]['entry_id'] == 'large'
452+
assert warnings[0][1]['row_input_bytes'] == len(encoded_lines[0])
453+
454+
455+
def test_write_table_rows_to_tabular_file_stringifies_nested_csv_values(tmp_path):
456+
rows_path = tmp_path / 'rows.ndjson'
457+
output_path = tmp_path / 'rows.csv'
458+
values = [['one', 'two'], None, ['three']]
459+
rows = [
460+
{
461+
'entry_id': f'entry_{index}',
462+
'upload_id': 'upload',
463+
'values': value,
464+
}
465+
for index, value in enumerate(values)
466+
]
467+
rows_path.write_text(
468+
''.join(f'{json.dumps(row, separators=(",", ":"))}\n' for row in rows),
469+
encoding='utf-8',
470+
)
471+
count = utils.write_table_rows_to_tabular_file(
472+
rows_path,
473+
output_path,
474+
{'values': Quantity(type=str, shape=['*'])},
475+
max_buffer_rows=2,
476+
)
477+
478+
with output_path.open(newline='', encoding='utf-8') as output_file:
479+
output_rows = list(csv.DictReader(output_file))
480+
assert count == len(rows)
481+
assert [row['entry_id'] for row in output_rows] == [row['entry_id'] for row in rows]
482+
assert [
483+
json.loads(row['values']) if row['values'] else None for row in output_rows
484+
] == values

0 commit comments

Comments
 (0)