mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-08-02 00:52:20 +00:00
Feature: add export package with StreamingManifestWriter and _dumps
This commit is contained in:
committed by
Trenton Holmes
parent
3a0ca03544
commit
b9a22d12cd
@@ -0,0 +1,43 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import TextIO
|
||||
|
||||
|
||||
def _dumps(content: list | dict) -> str:
|
||||
"""Serialize export JSON consistently across all sinks."""
|
||||
return json.dumps(content, cls=DjangoJSONEncoder, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
class StreamingManifestWriter:
|
||||
"""Incrementally writes a JSON array to a text handle, one record at a time.
|
||||
|
||||
Knows nothing about folders or zips: it writes the array framing and records
|
||||
to whatever handle the sink's ``stream()`` yields. The sink owns the handle's
|
||||
lifecycle (atomic rename, compare, spooling).
|
||||
"""
|
||||
|
||||
def __init__(self, handle: TextIO) -> None:
|
||||
self._file = handle
|
||||
self._first = True
|
||||
self._file.write("[")
|
||||
|
||||
def write_record(self, record: dict) -> None:
|
||||
if not self._first:
|
||||
self._file.write(",\n")
|
||||
else:
|
||||
self._first = False
|
||||
self._file.write(_dumps(record))
|
||||
|
||||
def write_batch(self, records: list[dict]) -> None:
|
||||
for record in records:
|
||||
self.write_record(record)
|
||||
|
||||
def close(self) -> None:
|
||||
"""Write the closing bracket. Does NOT close the handle (the sink owns it)."""
|
||||
self._file.write("\n]")
|
||||
@@ -0,0 +1,29 @@
|
||||
import io
|
||||
import json
|
||||
|
||||
from documents.export.sinks import StreamingManifestWriter
|
||||
from documents.export.sinks import _dumps
|
||||
|
||||
|
||||
class TestDumps:
|
||||
def test_dumps_is_indented_unicode_json(self) -> None:
|
||||
result: str = _dumps({"a": "é", "b": 1})
|
||||
assert '"é"' in result # ensure_ascii=False keeps unicode literal
|
||||
assert "\n" in result # indent=2 produces newlines
|
||||
assert json.loads(result) == {"a": "é", "b": 1}
|
||||
|
||||
|
||||
class TestStreamingManifestWriter:
|
||||
def test_writes_json_array_of_records(self) -> None:
|
||||
handle: io.StringIO = io.StringIO()
|
||||
writer: StreamingManifestWriter = StreamingManifestWriter(handle)
|
||||
writer.write_batch([{"pk": 1}, {"pk": 2}])
|
||||
writer.write_record({"pk": 3})
|
||||
writer.close()
|
||||
assert json.loads(handle.getvalue()) == [{"pk": 1}, {"pk": 2}, {"pk": 3}]
|
||||
|
||||
def test_empty_manifest_is_valid_empty_array(self) -> None:
|
||||
handle: io.StringIO = io.StringIO()
|
||||
writer: StreamingManifestWriter = StreamingManifestWriter(handle)
|
||||
writer.close()
|
||||
assert json.loads(handle.getvalue()) == []
|
||||
Reference in New Issue
Block a user