From 9c81fa29dca985ba3a9856a49ebdad0f01471744 Mon Sep 17 00:00:00 2001 From: Andrew Miller Date: Fri, 12 Jun 2026 23:49:20 -0500 Subject: [PATCH 1/2] Finalize README and update CHANGELOG for step 13 - Fix iter_listings import placement (moved to top of quick start block) - Guard listing.price formatting against None in sync and async examples - Add error handling section with RateLimitError/VisorAPIError snippet - Note that all responses are Pydantic models and SDK does not retry - CHANGELOG: move AsyncVisorClient, VisorClient, pagination helpers, and export audit from Future Work into Unreleased/Added Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 7 +++++-- README.md | 28 ++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ad77b4..17a96e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,11 +14,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `visor.models._base` — shared building blocks and `ListingsFilterBase` - `visor.models.listings` — `ListingsFilter` and listing response models - `visor.models.facets` — `FacetsFilter` and facet response models +- `AsyncVisorClient` — async HTTP client with context-manager support +- `VisorClient` — synchronous HTTP client with context-manager support +- Auto-pagination helpers: `paginate_listings`, `paginate_dealers` (async generators), + `iter_listings`, `iter_dealers` (sync iterators) +- Public export audit and test suite (`tests/test_exports.py`) ## Future Work -- HTTP client (`AsyncVisorClient`, `VisorClient`) -- Auto-pagination helpers - Retry-with-backoff strategy - MCP server wrapper - Response caching diff --git a/README.md b/README.md index 37ee081..ab06d66 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ pip install visor-python ## Quick start ```python -from visor import VisorClient, ListingsFilter +from visor import VisorClient, ListingsFilter, iter_listings with VisorClient() as client: # reads VISOR_API_KEY from env # Search for used Toyota trucks in Texas under $40k @@ -26,14 +26,14 @@ with VisorClient() as client: # reads VISOR_API_KEY from env ) ) for listing in page.data: - print(f"{listing.year} {listing.make} {listing.model} — ${listing.price:,}") + price = f"${listing.price:,}" if listing.price is not None else "N/A" + print(f"{listing.year} {listing.make} {listing.model} — {price}") # Look up a specific VIN vin = client.lookup_vin("4T1DAACKXTU765422", include=["price_history"]) print(vin.build.combined_msrp) # Paginate all results - from visor import iter_listings for listing in iter_listings(client, ListingsFilter(make=["Ford"], state=["TX"])): print(listing.vin) ``` @@ -50,7 +50,8 @@ async def main(): ListingsFilter(make=["Toyota"], state=["TX"], max_price=40_000) ) for listing in page.data: - print(listing.vin, listing.price) + price = f"${listing.price:,}" if listing.price is not None else "N/A" + print(listing.vin, price) asyncio.run(main()) ``` @@ -66,6 +67,25 @@ client = VisorClient(api_key="vsr_live_...") client = VisorClient() ``` +## Error handling + +All methods raise typed exceptions from `visor.exceptions`. Rate limit responses +include a `retry_after` hint; the SDK does not retry automatically. + +```python +from visor import VisorClient, ListingsFilter, RateLimitError, VisorAPIError + +with VisorClient() as client: + try: + page = client.filter_listings(ListingsFilter(make=["Toyota"])) + except RateLimitError as e: + print(f"Rate limited — retry after {e.retry_after}s") + except VisorAPIError as e: + print(f"API error {e.status_code}: {e}") +``` + +All responses are Pydantic models. + ## License MIT From 6f2f37a686239fb3800cbf6126744560c54146d6 Mon Sep 17 00:00:00 2001 From: Andrew Miller Date: Fri, 12 Jun 2026 23:52:26 -0500 Subject: [PATCH 2/2] Guard combined_msrp and retry_after None cases in README examples Co-Authored-By: Claude Sonnet 4.6 --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ab06d66..3a816e4 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,8 @@ with VisorClient() as client: # reads VISOR_API_KEY from env # Look up a specific VIN vin = client.lookup_vin("4T1DAACKXTU765422", include=["price_history"]) - print(vin.build.combined_msrp) + msrp = f"${vin.build.combined_msrp:,}" if vin.build.combined_msrp is not None else "N/A" + print(msrp) # Paginate all results for listing in iter_listings(client, ListingsFilter(make=["Ford"], state=["TX"])): @@ -79,7 +80,8 @@ with VisorClient() as client: try: page = client.filter_listings(ListingsFilter(make=["Toyota"])) except RateLimitError as e: - print(f"Rate limited — retry after {e.retry_after}s") + retry_after = f"{e.retry_after}s" if e.retry_after is not None else "later" + print(f"Rate limited — retry {retry_after}") except VisorAPIError as e: print(f"API error {e.status_code}: {e}") ```