Skip to content

Commit 5cadf14

Browse files
committed
feature(passport-worker): report progress
1 parent d61bd46 commit 5cadf14

4 files changed

Lines changed: 41 additions & 19 deletions

File tree

‎workers/passport-worker/passport_worker/activities.py‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
async_enter_cm,
1919
config_cache_key,
2020
enter_cm,
21+
to_raw_async_progress,
2122
)
23+
from icij_common.es import COUNT
2224

2325
from .aggregate import aggregate_results_act
2426
from .config import PassportWorkerConfig
@@ -336,21 +338,29 @@ async def export_passports(
336338
args: PassportExportArgs,
337339
*,
338340
progress: Annotated[ # noqa: ARG002
339-
AsyncProgressRateHandler | None, Weight(value=_WORKER_CONFIG_WEIGHT)
341+
AsyncProgressRateHandler | None, Weight(value=_BASE_WEIGHT)
340342
] = None,
341343
) -> PassportExportResponse:
342344
worker_config = cast(PassportWorkerConfig, lifespan_worker_config())
343345
paths = worker_config.paths
344346
output_path = paths.workdir / args.output_path
345347
es_client = lifespan_es_client()
346348
output_path.parent.mkdir(parents=True, exist_ok=True)
349+
n_docs = await es_client.count(args.docs)
347350
docs = (d async for d in get_docs(args.docs, args.project, es_client))
351+
n_docs = n_docs[COUNT]
352+
if progress is not None:
353+
if n_docs:
354+
progress = to_raw_async_progress(progress, max_progress=n_docs)
355+
else:
356+
progress = None
348357
await export_passports_act(
349358
docs,
350359
args.format,
351360
paths,
352361
output_path=output_path,
353362
ds_base_url=worker_config.ds_url,
363+
progress=progress,
354364
)
355365
export_path = output_path.relative_to(paths.workdir)
356366
res = PassportExportResponse(export_path=export_path)

‎workers/passport-worker/passport_worker/export.py‎

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import logging
22
from collections import defaultdict
33
from collections.abc import AsyncIterable, Iterable
4-
from contextlib import suppress
54
from io import StringIO
65
from pathlib import Path
76
from typing import Any, Never
@@ -10,8 +9,8 @@
109
from aiofile import async_open
1110
from datashare_python.exceptions import ArtifactNotFound
1211
from datashare_python.objects import Document, WorkerPaths
12+
from datashare_python.types_ import RawAsyncProgressHandler
1313
from datashare_python.utils import async_read_artifact
14-
from temporalio import activity
1514

1615
from .objects import ExportFormat, PassportArtifact, Passports
1716

@@ -25,40 +24,51 @@ async def export_passports_act(
2524
*,
2625
output_path: Path,
2726
ds_base_url: str,
27+
progress: RawAsyncProgressHandler | None = None,
2828
) -> None:
2929
match export_format:
3030
case ExportFormat.CSV:
31-
lines = export_as_csv(docs, ds_base_url=ds_base_url, paths=paths)
31+
lines = export_as_csv(
32+
docs, ds_base_url=ds_base_url, paths=paths, progress=progress
33+
)
3234
case ExportFormat.JSONL:
33-
lines = export_as_jsonl(docs, paths)
35+
lines = export_as_jsonl(docs, paths, progress=progress)
3436
case _:
3537
raise ValueError(f"unsupported export format {export_format}")
36-
l_count = 0
3738
async with async_open(output_path, "w") as f:
3839
async for line in lines:
3940
await f.write(line + "\n")
40-
l_count += 1
41-
if l_count % 1000 == 0:
42-
with suppress(RuntimeError):
43-
activity.heartbeat()
4441

4542

4643
async def export_as_jsonl(
47-
docs: AsyncIterable[Document], paths: WorkerPaths
44+
docs: AsyncIterable[Document],
45+
paths: WorkerPaths,
46+
progress: RawAsyncProgressHandler | None = None,
4847
) -> AsyncIterable[str]:
48+
n_docs = 0
4949
async for _, p in _read_passport_artifacts(paths, docs):
5050
yield p
51+
n_docs += 1
52+
if progress is not None and n_docs % 100 == 0:
53+
await progress(n_docs)
5154

5255

5356
async def export_as_csv(
54-
docs: AsyncIterable[Document], ds_base_url: str, paths: WorkerPaths
57+
docs: AsyncIterable[Document],
58+
ds_base_url: str,
59+
paths: WorkerPaths,
60+
progress: RawAsyncProgressHandler | None = None,
5561
) -> AsyncIterable[str]:
5662
out = StringIO()
63+
n_docs = 0
5764
records_by_project = defaultdict(list)
5865
async for doc, passports in _read_passport_artifacts(paths, docs):
59-
passports = Passports.model_validate_json(passports)
66+
passports = Passports.model_validate_json(passports) # noqa: PLW2901
6067
for r in _record_from_passports(doc, passports):
6168
records_by_project[r["project"]].append((doc, r))
69+
n_docs += 1
70+
if progress is not None and n_docs % 100 == 0:
71+
await progress(n_docs)
6272
recs = []
6373
for project, records in records_by_project.items():
6474
for doc, r in records:
@@ -82,7 +92,6 @@ async def _read_passport_artifacts(
8292
paths: WorkerPaths,
8393
docs: AsyncIterable[Document],
8494
) -> AsyncIterable[Document, bytes]:
85-
n_docs = 0
8695
async for doc in docs:
8796
try:
8897
passports = await async_read_artifact(
@@ -91,9 +100,6 @@ async def _read_passport_artifacts(
91100
except ArtifactNotFound:
92101
continue
93102
yield doc, passports
94-
n_docs += 1
95-
if n_docs % 100 == 0:
96-
logger.info(f"processed {n_docs} docs")
97103

98104

99105
def _record_from_passports(

‎workers/passport-worker/passport_worker/search.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ async def get_docs( # noqa: PLR0917
105105
yield d
106106

107107

108+
async def count_docs( # noqa: PLR0917
109+
docs: list[DocId] | DocumentSearchQuery | None, project: str, es_client: ESClient
110+
) -> dict[str, Any]:
111+
query = _build_doc_query(docs)
112+
counts = await es_client.count(query, index=project)
113+
return counts
114+
115+
108116
async def _write_preprocessing_batches(
109117
docs: AsyncIterable[Document],
110118
paths: WorkerPaths,

‎workers/passport-worker/tests/test_export.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from datashare_python.conftest import TEST_PROJECT
66
from datashare_python.objects import Document, WorkerPaths
77
from datashare_python.utils import write_artifact
8-
from icij_common.es import ESClient
98
from passport_service.objects import Passport
109
from passport_worker.config import PassportWorkerConfig
1110
from passport_worker.export import export_as_csv, export_as_jsonl
@@ -65,7 +64,6 @@ async def _async_iter[R](it: Iterable[R]) -> AsyncIterable[R]:
6564

6665
async def test_export_as_csv(
6766
artifact_dir_with_passports: tuple[WorkerPaths, list[Document]],
68-
test_es_client: ESClient,
6967
) -> None:
7068
# Given
7169
paths, docs = artifact_dir_with_passports

0 commit comments

Comments
 (0)