Sync and async clients are hand-maintained copies (~6,300 lines each side). Every change is made twice and the copies already drift (method order, formatting). Mechanically un-asyncing the async classes leaves only ~500 differing lines, mostly in code that is genuinely different: HTTP clients, StreamedLog, StatusMessageWatcher, RQ batch_add_requests.
Proposal: write async by hand, generate sync from it, commit both, and fail CI when they're out of sync. This is what httpcore, psycopg, pymongo and elasticsearch-py do. Tooling: unasync, unasyncd, or a small script in scripts/. The divergent parts listed above stay hand-written.
Alternatives are worse: sharing more logic in ResourceClientBase still duplicates signatures and docstrings, and wrapping async in an event loop for sync breaks inside running loops.
✍️ Drafted by Claude Code
Sync and async clients are hand-maintained copies (~6,300 lines each side). Every change is made twice and the copies already drift (method order, formatting). Mechanically un-asyncing the async classes leaves only ~500 differing lines, mostly in code that is genuinely different: HTTP clients,
StreamedLog,StatusMessageWatcher, RQbatch_add_requests.Proposal: write async by hand, generate sync from it, commit both, and fail CI when they're out of sync. This is what httpcore, psycopg, pymongo and elasticsearch-py do. Tooling: unasync, unasyncd, or a small script in
scripts/. The divergent parts listed above stay hand-written.Alternatives are worse: sharing more logic in
ResourceClientBasestill duplicates signatures and docstrings, and wrapping async in an event loop for sync breaks inside running loops.✍️ Drafted by Claude Code