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
3 changes: 2 additions & 1 deletion src/visor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from visor._client import AsyncVisorClient
from visor._client import AsyncVisorClient, VisorClient
from visor._pagination import (
iter_dealers,
iter_listings,
Expand Down Expand Up @@ -45,6 +45,7 @@
__all__ = [
# client
"AsyncVisorClient",
"VisorClient",
# pagination
"paginate_listings",
"paginate_dealers",
Expand Down
103 changes: 102 additions & 1 deletion src/visor/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from datetime import date
from typing import Literal

from visor._transport import DEFAULT_BASE_URL, AsyncVisorTransport
from visor._transport import DEFAULT_BASE_URL, AsyncVisorTransport, SyncVisorTransport
from visor.models.dealers import DealerDetail, DealerFilter, DealersPage
from visor.models.facets import FacetsFilter, FacetsResponse
from visor.models.listings import ListingDetail, ListingsFilter, ListingsPage
Expand Down Expand Up @@ -111,3 +111,104 @@ async def get_usage(
params["metering_class"] = ",".join(metering_class)
data = await self._transport.get("/usage", params)
return UsageSummary.model_validate(data)


class VisorClient:
def __init__(
self,
api_key: str | None = None,
timeout: float = 30.0,
base_url: str = DEFAULT_BASE_URL,
) -> None:
key = api_key or os.environ.get("VISOR_API_KEY")
if not key:
raise ValueError(
"api_key is required. Pass it directly or set VISOR_API_KEY."
)
self._transport = SyncVisorTransport(key, base_url=base_url, timeout=timeout)

def __enter__(self) -> "VisorClient":
return self

def __exit__(self, *args: object) -> None:
self.close()

def close(self) -> None:
self._transport.close()

# ------------------------------------------------------------------ #
# Inventory #
# ------------------------------------------------------------------ #

def filter_listings(self, filter: ListingsFilter | None = None) -> ListingsPage:
params = (filter or ListingsFilter()).to_params()
data = self._transport.get("/listings", params)
return ListingsPage.model_validate(data)

def get_listing(
self,
listing_id: str,
include: list[Literal["price_history", "options"]] | None = None,
) -> ListingDetail:
params: dict[str, str] = {}
if include:
params["include"] = ",".join(include)
data = self._transport.get(f"/listings/{listing_id}", params)
return ListingDetail.model_validate(data["data"])

def lookup_vin(
self,
vin: str,
include: list[Literal["price_history", "options"]] | None = None,
) -> VinDetail:
params: dict[str, str] = {}
if include:
params["include"] = ",".join(include)
data = self._transport.get(f"/vins/{vin}", params)
return VinDetail.model_validate(data["data"])

def filter_facets(self, filter: FacetsFilter) -> FacetsResponse:
data = self._transport.get("/facets", filter.to_params())
return FacetsResponse.model_validate(data)

# ------------------------------------------------------------------ #
# Dealers #
# ------------------------------------------------------------------ #

def search_dealers(self, filter: DealerFilter | None = None) -> DealersPage:
params = (filter or DealerFilter()).to_params()
data = self._transport.get("/dealers", params)
return DealersPage.model_validate(data)

def get_dealer(self, dealer_id: str) -> DealerDetail:
data = self._transport.get(f"/dealers/{dealer_id}")
return DealerDetail.model_validate(data["data"])

def dealer_inventory(
self,
dealer_id: str,
filter: ListingsFilter | None = None,
) -> ListingsPage:
params = (filter or ListingsFilter()).to_params()
data = self._transport.get(f"/dealers/{dealer_id}/listings", params)
return ListingsPage.model_validate(data)

# ------------------------------------------------------------------ #
# Usage #
# ------------------------------------------------------------------ #

def get_usage(
self,
start_date: date | None = None,
end_date: date | None = None,
metering_class: list[str] | None = None,
) -> UsageSummary:
params: dict[str, str] = {}
if start_date:
params["start_date"] = start_date.isoformat()
if end_date:
params["end_date"] = end_date.isoformat()
if metering_class:
params["metering_class"] = ",".join(metering_class)
data = self._transport.get("/usage", params)
return UsageSummary.model_validate(data)
Loading
Loading