feat(bib): backfill-comments skips sealed dockets; --force override (refs #615)
This commit is contained in:
@@ -396,6 +396,7 @@ def backfill_details(
|
||||
client: Client,
|
||||
*,
|
||||
docket: str = "",
|
||||
force: bool = False,
|
||||
limit: int | None = None,
|
||||
log_path: Path | None = None,
|
||||
commit_every: int = 25,
|
||||
@@ -417,6 +418,27 @@ def backfill_details(
|
||||
so we stop hammering it. Commits land every ``commit_every`` items
|
||||
so a crash loses at most that many items of work.
|
||||
"""
|
||||
if docket and not force:
|
||||
d = store.docket_get(docket)
|
||||
if d is not None and d.sealed:
|
||||
log.info(
|
||||
"%s sealed %s (%s); backfill skipped",
|
||||
docket,
|
||||
d.sealed_at[:10],
|
||||
d.seal_reason,
|
||||
)
|
||||
print(
|
||||
f"{docket}: sealed {d.sealed_at[:10]} ({d.seal_reason}); skipped — use --force",
|
||||
flush=True,
|
||||
)
|
||||
return {
|
||||
"skipped_sealed": 1,
|
||||
"seen": 0,
|
||||
"enriched": 0,
|
||||
"created": 0,
|
||||
"attached": 0,
|
||||
"errors": 0,
|
||||
}
|
||||
con = store._con() # noqa: SLF001
|
||||
# Resume rule: treat ``enriched:ok`` as the completion marker.
|
||||
# Using a tag (not just the abstract) lets us handle "see attached"
|
||||
@@ -675,6 +697,7 @@ def backfill_from_mirror(
|
||||
mirror: Mirror,
|
||||
*,
|
||||
docket: str,
|
||||
force: bool = False,
|
||||
limit: int | None = None,
|
||||
log_path: Path | None = None,
|
||||
commit_every: int = 200,
|
||||
@@ -688,6 +711,27 @@ def backfill_from_mirror(
|
||||
S3 fetches fan out over a thread pool; every Store/sqlite call stays
|
||||
on this thread. Idempotent: ``enriched:ok`` items are skipped.
|
||||
"""
|
||||
if docket and not force:
|
||||
d = store.docket_get(docket)
|
||||
if d is not None and d.sealed:
|
||||
log.info(
|
||||
"%s sealed %s (%s); backfill skipped",
|
||||
docket,
|
||||
d.sealed_at[:10],
|
||||
d.seal_reason,
|
||||
)
|
||||
print(
|
||||
f"{docket}: sealed {d.sealed_at[:10]} ({d.seal_reason}); skipped — use --force",
|
||||
flush=True,
|
||||
)
|
||||
return {
|
||||
"skipped_sealed": 1,
|
||||
"seen": 0,
|
||||
"enriched": 0,
|
||||
"created": 0,
|
||||
"attached": 0,
|
||||
"errors": 0,
|
||||
}
|
||||
con = store._con() # noqa: SLF001
|
||||
existing: dict[str, tuple[str, bool]] = {} # cid -> (key, enriched)
|
||||
for key, url, enriched in con.execute(
|
||||
|
||||
@@ -388,6 +388,11 @@ def backfill_comments(
|
||||
"reg.gov API — bulk speed, no rate cap, also ingests comments "
|
||||
"the original farm never listed. Requires --docket.",
|
||||
),
|
||||
force: bool = typer.Option(
|
||||
False,
|
||||
"--force",
|
||||
help="Run even if the docket is sealed.",
|
||||
),
|
||||
) -> None:
|
||||
"""Enrich every reg-gov comment stub with body + attachments.
|
||||
|
||||
@@ -413,6 +418,7 @@ def backfill_comments(
|
||||
store,
|
||||
Mirror(),
|
||||
docket=docket,
|
||||
force=force,
|
||||
limit=limit or None,
|
||||
log_path=log_path,
|
||||
)
|
||||
@@ -424,6 +430,7 @@ def backfill_comments(
|
||||
store,
|
||||
api,
|
||||
docket=docket,
|
||||
force=force,
|
||||
limit=limit or None,
|
||||
log_path=log_path,
|
||||
)
|
||||
|
||||
44
tests/bib/test_backfill_sealed.py
Normal file
44
tests/bib/test_backfill_sealed.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from bib.dockets import Docket
|
||||
from bib.regulations_gov import backfill_details, backfill_from_mirror
|
||||
from bib.store import Store
|
||||
|
||||
D = "CMS-2019-0111"
|
||||
|
||||
|
||||
def _sealed_store() -> Store:
|
||||
s = Store(":memory:", storage_dir="/tmp/nope")
|
||||
s.docket_upsert(
|
||||
Docket(id=D, sealed_at="2020-06-01T00:00:00Z", seal_reason="manual")
|
||||
)
|
||||
return s
|
||||
|
||||
|
||||
def test_api_backfill_skips_sealed_without_calls():
|
||||
s = _sealed_store()
|
||||
api = MagicMock()
|
||||
stats = backfill_details(s, api, docket=D)
|
||||
assert stats["skipped_sealed"] == 1 and stats["seen"] == 0
|
||||
api.get_comment_detail.assert_not_called()
|
||||
|
||||
|
||||
def test_mirror_backfill_skips_sealed_without_listing():
|
||||
s = _sealed_store()
|
||||
m = MagicMock()
|
||||
stats = backfill_from_mirror(s, m, docket=D)
|
||||
assert stats["skipped_sealed"] == 1 and stats["seen"] == 0
|
||||
m.comment_ids.assert_not_called()
|
||||
m.attachment_keys.assert_not_called()
|
||||
|
||||
|
||||
def test_force_runs_sealed_mirror_backfill():
|
||||
s = _sealed_store()
|
||||
m = MagicMock()
|
||||
m.comment_ids.return_value = []
|
||||
m.attachment_keys.return_value = {}
|
||||
stats = backfill_from_mirror(s, m, docket=D, force=True)
|
||||
assert "skipped_sealed" not in stats
|
||||
m.comment_ids.assert_called_once_with(D)
|
||||
Reference in New Issue
Block a user