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,
|
client: Client,
|
||||||
*,
|
*,
|
||||||
docket: str = "",
|
docket: str = "",
|
||||||
|
force: bool = False,
|
||||||
limit: int | None = None,
|
limit: int | None = None,
|
||||||
log_path: Path | None = None,
|
log_path: Path | None = None,
|
||||||
commit_every: int = 25,
|
commit_every: int = 25,
|
||||||
@@ -417,6 +418,27 @@ def backfill_details(
|
|||||||
so we stop hammering it. Commits land every ``commit_every`` items
|
so we stop hammering it. Commits land every ``commit_every`` items
|
||||||
so a crash loses at most that many items of work.
|
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
|
con = store._con() # noqa: SLF001
|
||||||
# Resume rule: treat ``enriched:ok`` as the completion marker.
|
# Resume rule: treat ``enriched:ok`` as the completion marker.
|
||||||
# Using a tag (not just the abstract) lets us handle "see attached"
|
# Using a tag (not just the abstract) lets us handle "see attached"
|
||||||
@@ -675,6 +697,7 @@ def backfill_from_mirror(
|
|||||||
mirror: Mirror,
|
mirror: Mirror,
|
||||||
*,
|
*,
|
||||||
docket: str,
|
docket: str,
|
||||||
|
force: bool = False,
|
||||||
limit: int | None = None,
|
limit: int | None = None,
|
||||||
log_path: Path | None = None,
|
log_path: Path | None = None,
|
||||||
commit_every: int = 200,
|
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
|
S3 fetches fan out over a thread pool; every Store/sqlite call stays
|
||||||
on this thread. Idempotent: ``enriched:ok`` items are skipped.
|
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
|
con = store._con() # noqa: SLF001
|
||||||
existing: dict[str, tuple[str, bool]] = {} # cid -> (key, enriched)
|
existing: dict[str, tuple[str, bool]] = {} # cid -> (key, enriched)
|
||||||
for key, url, enriched in con.execute(
|
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 "
|
"reg.gov API — bulk speed, no rate cap, also ingests comments "
|
||||||
"the original farm never listed. Requires --docket.",
|
"the original farm never listed. Requires --docket.",
|
||||||
),
|
),
|
||||||
|
force: bool = typer.Option(
|
||||||
|
False,
|
||||||
|
"--force",
|
||||||
|
help="Run even if the docket is sealed.",
|
||||||
|
),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Enrich every reg-gov comment stub with body + attachments.
|
"""Enrich every reg-gov comment stub with body + attachments.
|
||||||
|
|
||||||
@@ -413,6 +418,7 @@ def backfill_comments(
|
|||||||
store,
|
store,
|
||||||
Mirror(),
|
Mirror(),
|
||||||
docket=docket,
|
docket=docket,
|
||||||
|
force=force,
|
||||||
limit=limit or None,
|
limit=limit or None,
|
||||||
log_path=log_path,
|
log_path=log_path,
|
||||||
)
|
)
|
||||||
@@ -424,6 +430,7 @@ def backfill_comments(
|
|||||||
store,
|
store,
|
||||||
api,
|
api,
|
||||||
docket=docket,
|
docket=docket,
|
||||||
|
force=force,
|
||||||
limit=limit or None,
|
limit=limit or None,
|
||||||
log_path=log_path,
|
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