Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions colophon/anthropic.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import json
import os
import subprocess
import time
import urllib.error
import urllib.request

MODEL = "claude-haiku-4-5-20251001"
Expand Down Expand Up @@ -37,11 +39,21 @@ def _via_api(system, user, schema, key):
API, data=json.dumps(body).encode(),
headers={"x-api-key": key, "anthropic-version": "2023-06-01", "content-type": "application/json"},
)
resp = json.load(urllib.request.urlopen(req, timeout=120))
for block in resp.get("content", []):
if block.get("type") == "tool_use":
return block["input"]
raise RuntimeError("no tool_use block in Haiku response")
for attempt in range(5):
try:
with urllib.request.urlopen(req, timeout=120) as response:
resp = json.load(response)
for block in resp.get("content", []):
if block.get("type") == "tool_use":
return block["input"]
raise RuntimeError("no tool_use block in Haiku response")
except urllib.error.HTTPError as e:
if e.code == 429 and attempt < 4:
retry_after = e.headers.get("Retry-After")
sleep_time = int(retry_after) if (retry_after and retry_after.isdigit()) else (2 ** attempt * 2)
time.sleep(sleep_time)
continue
raise


def _via_cli(system, user, schema):
Expand Down
46 changes: 29 additions & 17 deletions colophon/hardcover.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import json
import os
import subprocess
import time
import urllib.error
import urllib.request

# Optional command that takes a GraphQL query on stdin and returns the JSON response,
Expand All @@ -31,23 +33,33 @@ def _api_key():

def query(graphql):
key = _api_key()
if key:
req = urllib.request.Request(
HC_ENDPOINT, data=json.dumps({"query": graphql}).encode(),
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
)
data = json.load(urllib.request.urlopen(req, timeout=60))
elif QUERY_SH: # delegate so the key never enters this process
r = subprocess.run([QUERY_SH], input=graphql, capture_output=True, text=True, timeout=60)
if r.returncode != 0:
raise HardcoverError(f"query command failed: {r.stderr.strip()[:200]}")
data = json.loads(r.stdout)
else:
raise HardcoverError("no Hardcover credentials — set COLOPHON_HARDCOVER_KEY "
"(or COLOPHON_HARDCOVER_QUERY_CMD)")
if data.get("errors"):
raise HardcoverError(f"hardcover errors: {data['errors']}")
return data.get("data") or {}
for attempt in range(5):
try:
if key:
req = urllib.request.Request(
HC_ENDPOINT, data=json.dumps({"query": graphql}).encode(),
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=60) as response:
data = json.load(response)
elif QUERY_SH: # delegate so the key never enters this process
r = subprocess.run([QUERY_SH], input=graphql, capture_output=True, text=True, timeout=60)
if r.returncode != 0:
raise HardcoverError(f"query command failed: {r.stderr.strip()[:200]}")
data = json.loads(r.stdout)
else:
raise HardcoverError("no Hardcover credentials — set COLOPHON_HARDCOVER_KEY "
"(or COLOPHON_HARDCOVER_QUERY_CMD)")
if data.get("errors"):
raise HardcoverError(f"hardcover errors: {data['errors']}")
return data.get("data") or {}
except urllib.error.HTTPError as e:
if e.code == 429 and attempt < 4:
retry_after = e.headers.get("Retry-After")
sleep_time = int(retry_after) if (retry_after and retry_after.isdigit()) else (2 ** attempt * 2)
time.sleep(sleep_time)
continue
raise


def book_by_id(hcid):
Expand Down
4 changes: 3 additions & 1 deletion colophon/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ def run_resolve(limit=None, book_ids=None, apply=False, min_conf=AUTO_CONF, g=No
books = [b for b, _ in audit.run_audit(limit=limit)["categories"].get("review-misseed", [])]
run_id = (store.new_run_id() + "-resolve") if (apply and store) else None
proposals, errors = [], 0
for b in books:
for i, b in enumerate(books):
if i > 0:
time.sleep(1.0)
p = resolve(b)
if (apply and p["action"] == "propose"
and (p.get("confidence") or 0) >= min_conf and p.get("isbn")):
Expand Down
Loading