### Development installation with git clone and uv sync Source: https://pantherale0.github.io/pysainsburys/getting-started/installation/ Clones the repository and installs dependencies in editable mode using uv sync. ```bash git clone https://github.com/pantherale0/pysainsburys.git cd pysainsburys uv sync ``` -------------------------------- ### Command line usage examples Source: https://pantherale0.github.io/pysainsburys/getting-started/quick-start/ Shows CLI commands for searching products, showing product details, viewing basket, and listing favourites. ```bash python -m pysainsburys product search bread python -m pysainsburys product show 3236048 python -m pysainsburys basket show python -m pysainsburys favourites list ``` -------------------------------- ### login(username=None, password=None, *, mfa_code=None, io_black_box=None, exchange_commerce=True) async Source: https://pantherale0.github.io/pysainsburys/api/reference/ Sign in via web credentials or start interactive browser login. If username and password are provided, it sends a login request, sends credentials, handles MFA if required, and optionally exchanges the commerce session. If credentials are not provided, it starts an interactive browser login and raises a BrowserLoginRequiredError. ```APIDOC ## login(username=None, password=None, *, mfa_code=None, io_black_box=None, exchange_commerce=True) async ### Description Sign in via web credentials or start interactive browser login. ### Method async function ### Signature `async def login(self, username: str | None = None, password: str | None = None, *, mfa_code: str | None = None, io_black_box: str | None = None, exchange_commerce: bool = True) -> dict[str, Any] | None` ### Parameters - **username** (str | None) - Optional - Web credentials username. - **password** (str | None) - Optional - Web credentials password. - **mfa_code** (str | None) - Optional - MFA verification code. - **io_black_box** (str | None) - Optional - IO black box token. - **exchange_commerce** (bool) - Optional - Whether to exchange the commerce session (default: True). ### Returns - **dict[str, Any] | None** - Login result dictionary or None if no MFA was required. ### Exceptions - **MFARequiredError** - Raised if MFA is required and no mfa_code is provided. - **BrowserLoginRequiredError** - Raised if interactive browser login is required. ``` -------------------------------- ### Search products without login Source: https://pantherale0.github.io/pysainsburys/getting-started/quick-start/ Asynchronous example that searches for products using the Sainsburys client with GOLAuth, printing product UID, name, and price. ```python import asyncio from pysainsburys import GOLAuth, Sainsburys async def main() -> None: async with Sainsburys(GOLAuth()) as client: results = await client.search_products("bread", page_size=5) for product in results.products: price = product.retail_price.price if product.retail_price else None print(product.product_uid, product.name, price) asyncio.run(main()) ``` -------------------------------- ### GOLAuth.login async method Source: https://pantherale0.github.io/pysainsburys/api/reference/ Signs in via web credentials or starts interactive browser login. If username and password are provided, it sends a login request, sends credentials, handles MFA if required, and optionally exchanges the commerce session. If no credentials are provided, it returns an authorization URL and raises BrowserLoginRequiredError. ```python async def login( self, username: str | None = None, password: str | None = None, *, mfa_code: str | None = None, io_black_box: str | None = None, exchange_commerce: bool = True, ) -> dict[str, Any] | None: """Sign in via web credentials or start interactive browser login.""" if username is not None and password is not None: await self.send_login_request() try: await self.send_credentials( username, password, io_black_box=io_black_box, ) except MFARequiredError: if mfa_code is None: raise return await self.send_mfa_request( mfa_code, io_black_box=io_black_box, exchange_commerce=exchange_commerce, ) if exchange_commerce: await self.exchange_commerce_session() return None authorization_url = await self.send_login_request() raise BrowserLoginRequiredError( "Open the authorization URL in a desktop browser, sign in, then " "call finish_login() with the redirect URL or authorization code.", authorization_url=authorization_url, ) ``` -------------------------------- ### Install pysainsburys with pip or uv Source: https://pantherale0.github.io/pysainsburys/getting-started/quick-start/ Install the pysainsburys package using pip, or from a checkout using uv sync. ```bash pip install pysainsburys # or, from a checkout: uv sync ``` -------------------------------- ### send_product_finder_request() async method Source: https://pantherale0.github.io/pysainsburys/api/reference/ Sends a public GET request to the Product Finder API. Builds the URL from PRODUCT_FINDER_BASE_URL and the given path, and passes optional params to the auth object. ```python async def send_product_finder_request( self, path: str, *, params: dict[str, str | int | float | bool] | None = None, ) -> dict[str, Any] | list[Any] | None: """Send a public request to the Product Finder API.""" url = PRODUCT_FINDER_BASE_URL + path return await self._auth.send_public_request( method="GET", url=url, params=params, ) ``` -------------------------------- ### async def login(username, password, *, mfa_code, io_black_box, exchange_commerce) Source: https://pantherale0.github.io/pysainsburys/api/reference/ Signs in via web credentials or starts an interactive browser login. If credentials are provided, it sends the login request, submits credentials, handles MFA if required, and optionally exchanges the commerce session. If no credentials are provided, it returns an authorization URL for browser-based login. ```APIDOC ## async def login(username, password, *, mfa_code, io_black_box, exchange_commerce) ### Description Sign in via web credentials or start interactive browser login. ### Method async def ### Parameters - **username** (str | None) - Optional - Username for login. - **password** (str | None) - Optional - Password for login. - **mfa_code** (str | None) - Optional - Multi-factor authentication code. - **io_black_box** (str | None) - Optional - Additional authentication data. - **exchange_commerce** (bool) - Optional - Whether to exchange commerce session after login. Defaults to True. ### Returns - **dict[str, Any] | None** - Token data if MFA was used, otherwise None. ### Raises - **MFARequiredError** - If MFA is required and no mfa_code is provided. - **BrowserLoginRequiredError** - If no credentials are provided, raises with an authorization URL. ``` -------------------------------- ### Slot listing and reservation (Python) Source: https://pantherale0.github.io/pysainsburys/user-guide/basic-usage/ Shows how to list delivery and collection slots, fetch a reservation, and reserve/validate a slot. The list call uses POST /slot/v2/slots with X-Http-Method-Override: GET. Location context is fetched automatically unless use_location_context=False is passed. ```python customer = await client.get_customer() week = await customer.slots.list_delivery() for slot in week.available_slots: print(slot.start_time, slot.price) collection = await customer.slots.list_collection(store_identifier="0474") reservation = await customer.slots.fetch_reservation() # Reserve a listed slot, or use the same method to change the current slot. reservation = await customer.slots.reserve(week.available_slots[0]) reservation = await customer.slots.validate() ``` -------------------------------- ### pysainsburys CLI example commands Source: https://pantherale0.github.io/pysainsburys/user-guide/basic-usage/ Example commands for the pysainsburys CLI. The --json flag prints the result as one JSON object including list metadata; --raw prints records as a JSON array with every public attribute. These flags cannot be combined. Session files default to ~/.config/pysainsburys/session.json, overridable with --session. ```bash python -m pysainsburys --help python -m pysainsburys product search bread --page 2 python -m pysainsburys product show 3236048 --json python -m pysainsburys product search bread --raw python -m pysainsburys basket add 3236048 --quantity 2 python -m pysainsburys favourites add 3236048 python -m pysainsburys slots list --type delivery python -m pysainsburys store search 2665 milk python -m pysainsburys auth refresh ``` -------------------------------- ### Search products with Sainsburys client Source: https://pantherale0.github.io/pysainsburys/ Example of using the pysainsburys client to search for products. It creates a Sainsburys instance with GOLAuth, searches for 'semi skimmed milk', and prints product UID, name, and retail price for each result. ```python import asyncio from pysainsburys import GOLAuth, Sainsburys async def main() -> None: async with Sainsburys(GOLAuth()) as client: results = await client.search_products("semi skimmed milk") for product in results.products: print(product.product_uid, product.name, product.retail_price) asyncio.run(main()) ``` -------------------------------- ### Install pysainsburys with pip Source: https://pantherale0.github.io/pysainsburys/getting-started/installation/ Installs the pysainsburys package from PyPI using pip. ```bash pip install pysainsburys ``` -------------------------------- ### Create Sainsburys client with async context manager Source: https://pantherale0.github.io/pysainsburys/user-guide/basic-usage/ Use this pattern to create a Sainsburys client with a GOLAuth session and automatically close it when finished. Requires a saved session file for authenticated endpoints. ```python from pysainsburys import GOLAuth, Sainsburys auth = await GOLAuth.from_session_file("~/.config/pysainsburys/session.json") async with Sainsburys(auth) as client: customer = await client.get_customer() basket = await customer.basket.fetch() ``` -------------------------------- ### Customer resources: fetch favourites, add favourite, fetch orders, get latest order status Source: https://pantherale0.github.io/pysainsburys/user-guide/basic-usage/ Shows how to use authenticated customer helpers to fetch favourites, add a favourite by product ID, fetch orders, and get the status of the latest order. Requires an authenticated client. ```python customer = await client.get_customer() favourites = await customer.favourites.fetch() await customer.favourites.add("3236048") orders = await customer.orders.fetch() status = await customer.orders.latest.status() ``` -------------------------------- ### GOLAuth.__init__ Source: https://pantherale0.github.io/pysainsburys/api/reference/ Initializes a GOLAuth instance with optional authentication tokens, cookies, and session. It sets up internal state and normalizes the wc_auth_token if not provided. ```APIDOC ## GOLAuth.__init__ ### Description Initializes a GOLAuth instance with optional authentication tokens, cookies, and session. It sets up internal state and normalizes the wc_auth_token if not provided. ### Method Constructor ### Parameters - **access_token** (str | None) - Optional - OAuth access token. - **refresh_token** (str | None) - Optional - OAuth refresh token. - **wc_auth_token** (str | None) - Optional - Web commerce auth token. - **user_id** (str | None) - Optional - User identifier. - **wc_trusted_token** (str | None) - Optional - Web commerce trusted token. - **cookies** (dict[str, str] | None) - Optional - Session cookies. - **app_version** (str | None) - Optional - Application version; defaults to the version from GOL_APP_USER_AGENT. - **login_hint** (str | None) - Optional - Login hint for OAuth. - **session** (aiohttp.ClientSession | None) - Optional - An existing aiohttp session; if not provided, a new one is created. ### Response No return value. ``` -------------------------------- ### GOLAuth.__aenter__ Source: https://pantherale0.github.io/pysainsburys/api/reference/ Enters the async context manager, returning the GOLAuth instance. ```APIDOC ## GOLAuth.__aenter__ ### Description Enters the async context manager, returning the GOLAuth instance. ### Method Async Context Manager Entry ### Returns - **GOLAuth** - The instance itself. ### Example ```python async with GOLAuth(...) as auth: ... ``` ``` -------------------------------- ### Search and get product details Source: https://pantherale0.github.io/pysainsburys/user-guide/basic-usage/ Search for products (public) and fetch product details with nutrition information (public). ```python # Search (public) results = await client.search_products("milk", page_number=1, page_size=24) # Detail with nutrition (public) product = await client.get_product("3236048") print(product.nutrition.summary if product.nutrition else "no nutrition data") ``` -------------------------------- ### Sainsburys.__init__ initialization with authenticator Source: https://pantherale0.github.io/pysainsburys/api/reference/ Initializes the Sainsburys client with an authenticated session. Sets up the API, customer placeholder, and update callbacks. ```python def __init__(self, authenticator: GOLAuth) -> None: """Initialize with an authenticated session.""" self.api = API(authenticator) self.customer: Customer | None = None self.updated_data_callbacks: list[Callable[[], Any]] = [] self._first_update = True ``` -------------------------------- ### GOLAuth._ensure_login_challenge method Source: https://pantherale0.github.io/pysainsburys/api/reference/ Starts the OAuth login flow and captures the Hydra login challenge. Raises AuthError if the challenge is not found. ```python async def _ensure_login_challenge(self) -> str: """Start the OAuth login flow and capture the Hydra login challenge.""" if self._login_challenge is not None: return self._login_challenge if self.authorization_url is None: await self.send_login_request() if self.authorization_url is None: raise AuthError("Authorization URL was not produced by the login request.") url = await self._follow_identity_redirects(self.authorization_url) challenge = login_challenge_from_url(url) if challenge is None and "/gol/login" in url: challenge = login_challenge_from_url(url) if challenge is None: raise AuthError("login_challenge not found in identity login redirect.") self._login_challenge = challenge self._login_referer = url return challenge ``` -------------------------------- ### Sainsburys.__init__ Source: https://pantherale0.github.io/pysainsburys/api/reference/ Initializes the Sainsburys client with an authenticated GOLAuth session. ```APIDOC ## Sainsburys.__init__ ### Description Initializes the Sainsburys client with an authenticated GOLAuth session. ### Method Constructor ### Signature `def __init__(self, authenticator: GOLAuth) -> None` ### Parameters - **authenticator** (GOLAuth) - Required - An authenticated GOLAuth session. ### Example ```python auth = await GOLAuth.from_session_file("~/.config/pysainsburys/session.json") client = Sainsburys(auth) ``` ``` -------------------------------- ### GOLAuth.from_pending_login_file async classmethod Source: https://pantherale0.github.io/pysainsburys/api/reference/ Loads in-progress login state from disk by reading a session file and delegating to from_pending_login_dict. ```python @classmethod async def from_pending_login_file(cls, path: str) -> GOLAuth: """Load in-progress login state from disk.""" return cls.from_pending_login_dict(await load_session_file(path)) ``` -------------------------------- ### SlotWeek.to_dict() Source: https://pantherale0.github.io/pysainsburys/api/reference/ Serialises the slot week to a plain dictionary, including the slot type value, week start date, store identifier, postcode, location UID, and a list of day dictionaries. ```APIDOC ## SlotWeek.to_dict() ### Description Serialises the slot week to a plain dictionary, including the slot type value, week start date, store identifier, postcode, location UID, and a list of day dictionaries. ### Method `to_dict` ### Signature `def to_dict(self) -> dict[str, Any]` ### Returns - **dict[str, Any]** - A dictionary representation of the slot week. ### Source Code ```python def to_dict(self) -> dict[str, Any]: """Serialise the slot week to a plain dictionary.""" return { "slot_type": self.slot_type.value if self.slot_type else None, "week_start_date": self.week_start_date, "store_identifier": self.store_identifier, "postcode": self.postcode, "location_uid": self.location_uid, "days": [day.to_dict() for day in self.days], } ``` ``` -------------------------------- ### login() async Source: https://pantherale0.github.io/pysainsburys/api/reference/ Prepares browser login and returns the authorization URL. ```APIDOC ## login() async ### Description Prepare browser login and return the authorization URL. ### Method async ### Signature `async def login(self) -> str` ### Parameters None ### Returns - **str** - The authorization URL. ``` -------------------------------- ### Slots Operations Source: https://pantherale0.github.io/pysainsburys/user-guide/basic-usage/ List delivery or click-and-collect availability, fetch the current reservation, reserve a slot, and validate the reservation. The list call uses POST /slot/v2/slots with X-Http-Method-Override: GET. Reservation writes use POST /slot/v1/slot/reservation and are experimental. ```APIDOC ## Slots Operations ### Description List delivery or click-and-collect availability, fetch the current reservation, reserve a slot, and validate the reservation. The list call uses POST /slot/v2/slots with X-Http-Method-Override: GET. Reservation writes use POST /slot/v1/slot/reservation and are experimental. ### Methods - `customer.slots.list_delivery()` - List available delivery slots. - `customer.slots.list_collection(store_identifier=...)` - List available collection slots for a store. - `customer.slots.fetch_reservation()` - Fetch the current reservation. - `customer.slots.reserve(slot)` - Reserve a listed slot or change the current slot. - `customer.slots.validate()` - Validate the current reservation. ### Parameters - `store_identifier` (string) - Required for collection - The store identifier (e.g., "0474"). - `slot` (object) - Required - A slot object from the available slots list. ### Request Example ```python customer = await client.get_customer() week = await customer.slots.list_delivery() for slot in week.available_slots: print(slot.start_time, slot.price) collection = await customer.slots.list_collection(store_identifier="0474") reservation = await customer.slots.fetch_reservation() reservation = await customer.slots.reserve(week.available_slots[0]) reservation = await customer.slots.validate() ``` ### CLI Examples ``` pysainsburys slots list --type delivery pysainsburys slots list --type collection --store 0474 pysainsburys slots reservation pysainsburys slots reserve SLOT_UID --type delivery pysainsburys slots validate pysainsburys slots context ``` ### Notes The list call uses `POST /slot/v2/slots` with `X-Http-Method-Override: GET`. Location context is fetched automatically unless `--no-context` is passed on the CLI or `use_location_context=False` in Python. Slot reservation writes use the statically inferred `POST /slot/v1/slot/reservation` payload. They have not yet been verified against a live commerce session and should be treated as experimental. ``` -------------------------------- ### GOLAuth.from_pending_login_file classmethod Source: https://pantherale0.github.io/pysainsburys/api/reference/ Loads in-progress login state from disk by reading the session file and converting it via from_pending_login_dict. ```python @classmethod async def from_pending_login_file(cls, path: str) -> GOLAuth: """Load in-progress login state from disk.""" return cls.from_pending_login_dict(await load_session_file(path)) ``` -------------------------------- ### send_login_request async method Source: https://pantherale0.github.io/pysainsburys/api/reference/ Prepares browser login by fetching OIDC configuration and returning the authorization URL. ```python async def send_login_request(self) -> str: """Prepare browser login and return the authorization URL.""" await self.fetch_oidc_configuration() return self.build_authorization_url() ``` -------------------------------- ### bind_api method Source: https://pantherale0.github.io/pysainsburys/api/reference/ Attaches a client for in-store product lookups. Returns the Store instance for chaining. ```python def bind_api(self, api: API) -> Store: """Attach a client for in-store product lookups.""" self._api = api return self ``` -------------------------------- ### Slot CLI commands Source: https://pantherale0.github.io/pysainsburys/user-guide/basic-usage/ CLI commands for listing delivery/collection slots, fetching reservation, reserving a slot, validating, and showing context. The list call uses POST /slot/v2/slots with X-Http-Method-Override: GET. Location context is fetched automatically unless --no-context is passed. ```bash pysainsburys slots list --type delivery pysainsburys slots list --type collection --store 0474 pysainsburys slots reservation pysainsburys slots reserve SLOT_UID --type delivery pysainsburys slots validate pysainsburys slots context ``` -------------------------------- ### GOLAuth.send_login_request method Source: https://pantherale0.github.io/pysainsburys/api/reference/ Prepares browser login by fetching OIDC configuration and returning the authorization URL. ```python async def send_login_request(self) -> str: """Prepare browser login and return the authorization URL.""" await self.fetch_oidc_configuration() return self.build_authorization_url() ``` -------------------------------- ### SlotWeek dataclass definition and methods Source: https://pantherale0.github.io/pysainsburys/api/reference/ Defines the SlotWeek dataclass with fields for slot type, week start date, store identifier, postcode, location UID, and days. Includes a from_dict classmethod to parse JSON, properties to flatten slots, and methods for serialization and iteration. ```python @dataclass(slots=True) class SlotWeek: """ Week view of delivery or collection slots. Attributes: slot_type: Requested slot type (``delivery`` or ``collection``). week_start_date: First day of the returned week when provided. store_identifier: Fulfilment store number used for the query. postcode: Delivery postcode context when applicable. location_uid: Click-and-collect location uid when applicable. days: Day groupings with nested slot windows. """ slot_type: SlotType | None = None week_start_date: str | None = None store_identifier: str | None = None postcode: str | None = None location_uid: str | None = None days: list[SlotDay] = field(default_factory=list) @classmethod def from_dict( cls, data: dict[str, Any], *, slot_type: SlotType | None = None, store_identifier: str | None = None, postcode: str | None = None, location_uid: str | None = None, ) -> SlotWeek: """Parse a slot week from grocery API JSON.""" days_data = data.get("days") or data.get("slot_days") if days_data is None: weeks = data.get("weeks") or data.get("slot_weeks") if isinstance(weeks, list): days_data = [] for week in weeks: if isinstance(week, dict): days_data.extend(week.get("days", [])) days = [ SlotDay.from_dict(item) for item in (days_data or []) if isinstance(item, dict) ] return cls( slot_type=slot_type, week_start_date=data.get("week_start_date") or data.get("start_date"), store_identifier=store_identifier or data.get("store_identifier"), postcode=postcode or data.get("postcode"), location_uid=location_uid or data.get("location_uid"), days=days, ) @property def slots(self) -> list[DeliverySlot]: """Flatten all slots across days.""" return [slot for day in self.days for slot in day.slots] @property def available_slots(self) -> list[DeliverySlot]: """Flatten only available slots across days.""" return [slot for slot in self.slots if slot.is_available] def to_dict(self) -> dict[str, Any]: """Serialise the slot week to a plain dictionary.""" return { "slot_type": self.slot_type.value if self.slot_type else None, "week_start_date": self.week_start_date, "store_identifier": self.store_identifier, "postcode": self.postcode, "location_uid": self.location_uid, "days": [day.to_dict() for day in self.days], } def __iter__(self) -> Iterator[tuple[str, Any]]: """Allow ``dict(slot_week)`` conversion.""" return iter(self.to_dict().items()) ``` -------------------------------- ### login() async method Source: https://pantherale0.github.io/pysainsburys/api/reference/ Prepares browser login and returns the authorization URL by delegating to the auth object's send_login_request method. ```python async def login(self) -> str: """Prepare browser login and return the authorization URL.""" return await self._auth.send_login_request() ``` -------------------------------- ### Store.search_products(keyword, *, page=1, page_size=20) Source: https://pantherale0.github.io/pysainsburys/api/reference/ Asynchronously searches in-store products with aisle and stock information for this store. It sends a product finder request and returns a StoreProductList. ```APIDOC ## Store.search_products(keyword, *, page=1, page_size=20) ### Description Search in-store products with aisle and stock for this store. This async method sends a product finder request to the API and returns a list of products. ### Method async ### Parameters - **keyword** (str) - Required - The search keyword for products. - **page** (int) - Optional - The page number for pagination (default: 1). - **page_size** (int) - Optional - The number of products per page (default: 20). ### Returns - **StoreProductList** - The list of products found. ### Example ```python products = await store.search_products("milk") ``` ``` -------------------------------- ### Fetch product detail and nutrition Source: https://pantherale0.github.io/pysainsburys/getting-started/quick-start/ Fetches a product by ID and prints nutrition summary items. Requires an authenticated client. ```python product = await client.get_product("3236048") if product.nutrition: for item in product.nutrition.summary: print(item.name, item.values, item.level) ``` -------------------------------- ### GOLAuth.build_authorization_url method Source: https://pantherale0.github.io/pysainsburys/api/reference/ Builds a PKCE authorization URL for browser-based login. Generates a PKCE verifier, code challenge, and OAuth state, then constructs the URL with required parameters. ```python def build_authorization_url(self) -> str: """Build a PKCE authorization URL for browser-based login.""" self._pkce_verifier = random_string(43, 128) code_challenge = build_code_challenge(self._pkce_verifier) self._oauth_state = secrets.token_urlsafe(32) params: dict[str, str] = { "client_id": AUTH_CLIENT_ID, "response_type": "code", "redirect_uri": AUTH_REDIRECT_URI, "scope": AUTH_SCOPE, "code_challenge": code_challenge, "code_challenge_method": AUTH_CODE_CHALLENGE_METHOD, "state": self._oauth_state, **AUTH_EXTRA_PARAMS, } if self.login_hint: params["login_hint"] = self.login_hint authorization_url = ( f"{self.authorization_endpoint}?{urllib.parse.urlencode(params)}" ) self.authorization_url = authorization_url return authorization_url ``` -------------------------------- ### Sainsburys.__init__(authenticator) Source: https://pantherale0.github.io/pysainsburys/api/reference/ Initialize with an authenticated session. Sets up the API client and initializes customer data. ```APIDOC ## Sainsburys.__init__(authenticator) ### Description Initialize with an authenticated session. ### Method Constructor ### Signature `def __init__(self, authenticator: GOLAuth) -> None` ### Parameters - **authenticator** (GOLAuth) - Required - The authenticator object used to create an authenticated session. ### Returns - **None** - This method returns nothing. ``` -------------------------------- ### Authenticated session usage with Sainsburys client Source: https://pantherale0.github.io/pysainsburys/api/reference/ Shows how to create an authenticated Sainsburys client using GOLAuth.from_session_file and fetch customer data and basket. Requires a valid session file at the specified path. ```python auth = await GOLAuth.from_session_file( "~/.config/pysainsburys/session.json" ) async with Sainsburys(auth) as client: customer = await client.get_customer() basket = await customer.basket.fetch() ``` -------------------------------- ### bind_products(api, products) Source: https://pantherale0.github.io/pysainsburys/api/reference/ Attach an API client to each product in a list. ```APIDOC ## bind_products(api, products) ### Description Attach an API client to each product in a list. ### Method Function ### Parameters - **api** (API) - The API client to attach. - **products** (list[Product]) - The list of products to bind the API client to. ### Returns - **list[Product]** - The list of products with the API client attached. ### Source Code ```python def bind_products(api: API, products: list[Product]) -> list[Product]: """Attach an API client to each product in a list.""" for product in products: bind_product(api, product) return products ``` ``` -------------------------------- ### bind_products(api, products) Source: https://pantherale0.github.io/pysainsburys/api/reference/ Attach an API client to each product in a list. ```APIDOC ## bind_products(api, products) ### Description Attach an API client to each product in a list. ### Parameters - **api** (API) - Required - The API client to attach. - **products** (list[Product]) - Required - The list of products to bind. ### Returns - **list[Product]** - The list of products with the API client attached. ### Example ```python products = bind_products(api, products) ``` ``` -------------------------------- ### GOLAuth.from_pending_login_file Source: https://pantherale0.github.io/pysainsburys/api/reference/ Loads an in-progress login state from a file asynchronously and returns a GOLAuth instance. ```APIDOC ## GOLAuth.from_pending_login_file ### Description Loads an in-progress login state from a file asynchronously and returns a GOLAuth instance. ### Method Async Class Method ### Parameters - **path** (str) - Required - File path to load the pending login state from. ### Returns - **GOLAuth** - Restored authentication instance. ### Example ```python auth = await GOLAuth.from_pending_login_file('pending.json') ``` ``` -------------------------------- ### search_products(keyword, *, page_number=1, page_size=24) Source: https://pantherale0.github.io/pysainsburys/api/reference/ Asynchronously searches for products by keyword with optional pagination. No login is required. Returns a ProductList object. ```APIDOC ## search_products(keyword, *, page_number=1, page_size=24) ### Description Search products by keyword (no login required). ### Method async ### Signature `async def search_products(self, keyword: str, *, page_number: int = 1, page_size: int = 24) -> ProductList` ### Parameters - **keyword** (str) - Required - The search keyword. - **page_number** (int) - Optional - The page number to retrieve (default: 1). - **page_size** (int) - Optional - The number of products per page (default: 24). ### Returns - **ProductList** - A list of products matching the search criteria. ### Errors - Raises `TypeError` if the response is not a JSON object. ``` -------------------------------- ### Product.bind_api Source: https://pantherale0.github.io/pysainsburys/api/reference/ Attach a client for basket and favourites operations. ```APIDOC ## Product.bind_api ### Description Attach a client for basket and favourites operations. ### Method bind_api ### Signature ```python def bind_api(self, api: API) -> Product: ``` ### Parameters - **api** (API) - Required - The API client to attach. ### Returns - **Product** - The Product instance with the API attached. ``` -------------------------------- ### main() function Source: https://pantherale0.github.io/pysainsburys/api/reference/ Runs the CLI. Parses arguments, sets up logging based on verbosity, and executes the command asynchronously. Catches known exceptions and prints errors to stderr, returning exit codes 1 for errors and 130 for keyboard interrupts. ```python def main(argv: list[str] | None = None) -> int: """Run the CLI.""" args = parse_args(argv) if args.verbose: logging.basicConfig(level=logging.DEBUG) else: logging.basicConfig(level=logging.WARNING) try: return asyncio.run(run_command(args)) except ( AuthError, BrowserLoginRequiredError, HttpException, SessionRequiredError, ValueError, TypeError, ) as exc: print(str(exc), file=sys.stderr) return 1 except KeyboardInterrupt: print("Interrupted.", file=sys.stderr) return 130 ``` -------------------------------- ### bind_api(api) Source: https://pantherale0.github.io/pysainsburys/api/reference/ Attaches a client (API instance) to the Product for basket and favourites operations. Returns the Product instance for chaining. ```APIDOC ## bind_api(api) ### Description Attach a client for basket and favourites operations. ### Method bind_api ### Signature `def bind_api(self, api: API) -> Product` ### Parameters - **api** (API) - Required - The API client to attach. ### Returns - **Product** - The Product instance with the API attached. ### Source Code ```python def bind_api(self, api: API) -> Product: """Attach a client for basket and favourites operations.""" self._api = api return self ``` ``` -------------------------------- ### bind_api(api) Source: https://pantherale0.github.io/pysainsburys/api/reference/ Attaches a client (API instance) to the Product for basket and favourites operations. Returns the Product instance for chaining. ```APIDOC ## bind_api(api) ### Description Attach a client for basket and favourites operations. ### Method bind_api ### Signature bind_api(self, api: API) -> Product ### Parameters - **api** (API) - Required - The API client to attach. ### Returns - **Product** - The Product instance with the API attached. ### Source Code ```python def bind_api(self, api: API) -> Product: """Attach a client for basket and favourites operations.""" self._api = api return self ``` ``` -------------------------------- ### bind_api(api) Source: https://pantherale0.github.io/pysainsburys/api/reference/ Attaches a client (API instance) to the Product for basket and favourites operations. Returns the Product instance for chaining. ```APIDOC ## bind_api(api) ### Description Attach a client for basket and favourites operations. ### Method bind_api ### Signature bind_api(self, api: API) -> Product ### Parameters - **api** (API) - Required - The client instance to attach for basket and favourites operations. ### Returns - **Product** - The Product instance with the client attached. ### Source Code ```python def bind_api(self, api: API) -> Product: """Attach a client for basket and favourites operations.""" self._api = api return self ``` ``` -------------------------------- ### __aenter__ Source: https://pantherale0.github.io/pysainsburys/api/reference/ Enters the async context manager. ```APIDOC ## __aenter__ ### Description Enters the async context manager and returns the GOLAuth instance. ### Method async ### Signature ```python async def __aenter__(self) -> GOLAuth: ``` ### Returns - **GOLAuth** - The instance itself. ``` -------------------------------- ### GOLAuth.from_pending_login_file(path) Source: https://pantherale0.github.io/pysainsburys/api/reference/ Loads in-progress login state from disk. This async class method reads a session file and restores the pending login state. ```APIDOC ## GOLAuth.from_pending_login_file(path) ### Description Loads in-progress login state from disk. This async class method reads a session file and restores the pending login state. ### Method async classmethod ### Signature `from_pending_login_file(cls, path: str) -> GOLAuth` ### Parameters - **path** (str) - Required - The file path to the session file containing pending login state. ### Behavior - Reads the session file using `load_session_file`. - Calls `from_pending_login_dict` with the loaded data. ### Returns - **GOLAuth** - An authentication object with pending login state restored. ``` -------------------------------- ### Store.search_products async method Source: https://pantherale0.github.io/pysainsburys/api/reference/ Searches in-store products with aisle and stock information for the store. Sends a product finder request to '/v2/products' with storeId, keyword, page, and size parameters. Raises TypeError if the response is not a JSON object, and returns a StoreProductList parsed from the response. ```python async def search_products( self, keyword: str, *, page: int = 1, page_size: int = 20, ) -> StoreProductList: """Search in-store products with aisle and stock for this store.""" response = await self._require_api().send_product_finder_request( "/v2/products", params={ "storeId": self.store_id, "keyword": keyword, "page": page, "size": page_size, }, ) if not isinstance(response, dict): msg = "Store product search response was not a JSON object." raise TypeError(msg) return StoreProductList.from_dict(response) ``` -------------------------------- ### search_products(keyword, page_number=1, page_size=24) Source: https://pantherale0.github.io/pysainsburys/api/reference/ Searches for products by keyword. No login is required. Returns a ProductList object. ```APIDOC ## search_products(keyword, page_number=1, page_size=24) ### Description Search products by keyword. No login required. ### Method async def search_products(self, keyword: str, *, page_number: int = 1, page_size: int = 24) -> ProductList ### Parameters - **keyword** (str) - Required - The search keyword. - **page_number** (int) - Optional - Page number for pagination (default: 1). - **page_size** (int) - Optional - Number of results per page (default: 24). ### Returns - **ProductList** - A list of products matching the keyword. ### Example ```python product_list = await sainsburys.search_products("milk") ``` ``` -------------------------------- ### Sainsburys.__aenter__() Source: https://pantherale0.github.io/pysainsburys/api/reference/ Enter async context manager. Returns self to allow use as an async context manager. ```APIDOC ## Sainsburys.__aenter__() ### Description Enter async context manager. ### Method async ### Signature `async def __aenter__(self) -> Sainsburys` ### Returns - **Sainsburys** - The Sainsburys instance itself. ``` -------------------------------- ### bind_api Source: https://pantherale0.github.io/pysainsburys/api/reference/ Attaches a client for in-store product lookups to the store instance. ```APIDOC ## bind_api ### Description Attach a client for in-store product lookups. ### Method def bind_api(self, api: API) -> Store ### Parameters - **api** (API) - Required - The API client to attach. ### Returns - **Store** - The store instance with the API client attached. ``` -------------------------------- ### YourNectarPrices.all_offers (property) Source: https://pantherale0.github.io/pysainsburys/api/reference/ Returns a list combining opted-in and not-opted-in offers. ```APIDOC ## YourNectarPrices.all_offers (property) ### Description Returns a list combining opted-in and not-opted-in offers. ### Returns - **list[YourNectarPriceOffer]** - The combined list of offers. ### Example ```python all_offers = prices.all_offers ``` ``` -------------------------------- ### GOLAuth.build_authorization_url PKCE authorization URL Source: https://pantherale0.github.io/pysainsburys/api/reference/ Builds a PKCE authorization URL for browser-based login. Generates a PKCE verifier, code challenge, and OAuth state, then constructs the URL with required parameters. Stores the URL in self.authorization_url and returns it. ```python def build_authorization_url(self) -> str: """Build a PKCE authorization URL for browser-based login.""" self._pkce_verifier = random_string(43, 128) code_challenge = build_code_challenge(self._pkce_verifier) self._oauth_state = secrets.token_urlsafe(32) params: dict[str, str] = { "client_id": AUTH_CLIENT_ID, "response_type": "code", "redirect_uri": AUTH_REDIRECT_URI, "scope": AUTH_SCOPE, "code_challenge": code_challenge, "code_challenge_method": AUTH_CODE_CHALLENGE_METHOD, "state": self._oauth_state, **AUTH_EXTRA_PARAMS, } if self.login_hint: params["login_hint"] = self.login_hint authorization_url = ( f"{self.authorization_endpoint}?{urllib.parse.urlencode(params)}" ) self.authorization_url = authorization_url return authorization_url ``` -------------------------------- ### get_product(product_uid) Source: https://pantherale0.github.io/pysainsburys/api/reference/ Asynchronously fetches a single product by its unique identifier (UID). No login is required. Returns a Product object. ```APIDOC ## get_product(product_uid) ### Description Fetch a single product by uid (no login required). ### Method async ### Signature `async def get_product(self, product_uid: str) -> Product` ### Parameters - **product_uid** (str) - Required - The unique identifier of the product to fetch. ### Returns - **Product** - The product object corresponding to the given UID. ### Errors - Raises `TypeError` if the response is not a JSON object or the product payload is not a JSON object. ``` -------------------------------- ### send_product_finder_request(path, *, params=None) async Source: https://pantherale0.github.io/pysainsburys/api/reference/ Sends a public request to the Product Finder API. ```APIDOC ## send_product_finder_request(path, *, params=None) async ### Description Send a public request to the Product Finder API. ### Method async ### Signature `async def send_product_finder_request(self, path: str, *, params: dict[str, str | int | float | bool] | None = None) -> dict[str, Any] | list[Any] | None` ### Parameters #### Positional Parameters - **path** (str) - Required - The path to append to the Product Finder base URL. #### Keyword-Only Parameters - **params** (dict[str, str | int | float | bool] | None) - Optional - Query parameters to include in the request. ### Returns - **dict[str, Any] | list[Any] | None** - The response data. ``` -------------------------------- ### GOLAuth._browser_headers method Source: https://pantherale0.github.io/pysainsburys/api/reference/ Returns browser-like headers for identity provider requests, optionally adding a Referer header and setting sec-fetch-site to same-origin when a referer is provided. ```python def _browser_headers(self, *, referer: str | None = None) -> dict[str, str]: """Return browser-like headers for identity provider requests.""" headers = dict(BROWSER_HEADERS) if referer is not None: headers["Referer"] = referer headers["sec-fetch-site"] = "same-origin" return headers ```