Skip to content

Reference

Cookidoo API package.

Cookidoo

Unofficial Cookidoo API interface.

Init function for Cookidoo API.

Parameters:

Name Type Description Default
session ClientSession

The client session for aiohttp requests. Must use a CookieJar(unsafe=True) to support cross-domain cookies during the OAuth2 login flow.

required
cfg CookidooConfig

Cookidoo config

CookidooConfig()
on_auth_data_update Callable[[CookidooAuthData], None] | None

Optional callback invoked with the new :class:CookidooAuthData whenever the tokens change, i.e. after a login and after every (transparent) refresh. Use it to keep a persisted copy in sync without having to poll :attr:auth_data around every call. See :attr:on_auth_data_update.

None
Source code in cookidoo_api/cookidoo.py
def __init__(
    self,
    session: ClientSession,
    cfg: CookidooConfig = CookidooConfig(),
    on_auth_data_update: Callable[[CookidooAuthData], None] | None = None,
) -> None:
    """Init function for Cookidoo API.

    Parameters
    ----------
    session
        The client session for aiohttp requests.
        Must use a ``CookieJar(unsafe=True)`` to support cross-domain
        cookies during the OAuth2 login flow.
    cfg
        Cookidoo config
    on_auth_data_update
        Optional callback invoked with the new :class:`CookidooAuthData`
        whenever the tokens change, i.e. after a login and after every
        (transparent) refresh. Use it to keep a persisted copy in sync
        without having to poll :attr:`auth_data` around every call. See
        :attr:`on_auth_data_update`.

    """
    self._session = session
    self._cfg = cfg
    self._on_auth_data_update = on_auth_data_update
    self._api_headers = DEFAULT_API_HEADERS.copy()
    self._logged_in = False
    self._endpoint_overrides = {}
    self._endpoints_resolved = False
    self._endpoints_lock = asyncio.Lock()
    self._token_lock = asyncio.Lock()
    self._refresh_token = None
    self._expires_at = 0.0
    self._oidc = None
    self._rmi_links = None
    self._pending_login = None
    self._login_generation = 0

localization property

Localization.

api_endpoint property

Get the api endpoint.

Returns the cookidoo domain derived from the localization URL, e.g. https://cookidoo.ch or https://cookidoo.co.uk.

auth_data property

The current OAuth2 tokens, for persistence. None until logged in.

on_auth_data_update property writable

The callback notified whenever the tokens change.

Called with the new :class:CookidooAuthData after a login and after every refresh, including the transparent one a request performs when the access token has expired. The server rotates the refresh token along with the access token, so a consumer that persists the tokens should store what the callback hands it, rather than only the result of an explicit :meth:login.

Not called by :meth:apply_auth_data or :meth:load_token, which restore tokens the consumer already holds.

Exceptions raised by the callback are caught and logged: a consumer failing to store the tokens must not break the request that triggered the refresh.

apply_auth_data

Restore a previous login from persisted tokens (no network call).

The access token is refreshed automatically on the next request if it has expired.

Source code in cookidoo_api/cookidoo.py
def apply_auth_data(self, auth_data: CookidooAuthData) -> None:
    """Restore a previous login from persisted tokens (no network call).

    The access token is refreshed automatically on the next request if it
    has expired.
    """
    self._api_headers["Authorization"] = f"Bearer {auth_data.access_token}"
    self._refresh_token = auth_data.refresh_token
    self._expires_at = auth_data.expires_at
    self._logged_in = True

begin_login async

Return an authorization URL for a browser or another external agent.

No email/password is submitted. State and the PKCE verifier stay in this instance for five minutes. Starting again invalidates the old attempt. The caller opens the URL and passes the full registered redirect URL to :meth:complete_login.

This method does not launch a browser or register a redirect handler. It cannot make an unregistered redirect URI acceptable to Cookidoo.

Source code in cookidoo_api/cookidoo.py
async def begin_login(self) -> str:
    """Return an authorization URL for a browser or another external agent.

    No email/password is submitted. State and the PKCE verifier stay in
    this instance for five minutes. Starting again invalidates the old
    attempt. The caller opens the URL and passes the full registered
    redirect URL to :meth:`complete_login`.

    This method does not launch a browser or register a redirect handler.
    It cannot make an unregistered redirect URI acceptable to Cookidoo.
    """
    self.cancel_login()
    generation = self._login_generation
    self._assert_oauth_client()
    try:
        oidc = await self._discovery()
        authorization_endpoint = oidc["authorization_endpoint"]
        token_endpoint = oidc["token_endpoint"]
        self._assert_ciam_origin(authorization_endpoint)
        self._assert_ciam_origin(token_endpoint)
    except TimeoutError as e:
        raise CookidooRequestException("Authorization discovery timed out.") from e
    except ClientError as e:
        raise CookidooRequestException("Authorization discovery failed.") from e

    self._assert_login_generation(generation)

    verifier, challenge = self._pkce_pair()
    state = secrets.token_urlsafe(32)
    self._pending_login = _PendingLogin(
        state=state,
        verifier=verifier,
        expires_at=time.monotonic() + 300,
        token_endpoint=token_endpoint,
        client_id=self._cfg.client_id,
        redirect_uri=self._cfg.redirect_uri,
    )
    return str(
        URL(authorization_endpoint).with_query(
            {
                "response_type": "code",
                "client_id": self._cfg.client_id,
                "redirect_uri": self._cfg.redirect_uri,
                "market": self._cfg.localization.country_code,
                "scope": OAUTH_SCOPE,
                "state": state,
                "code_challenge": challenge,
                "code_challenge_method": "S256",
                "ui_locales": self._cfg.localization.language,
            }
        )
    )

cancel_login

Discard a pending browser login without changing existing tokens.

Source code in cookidoo_api/cookidoo.py
def cancel_login(self) -> None:
    """Discard a pending browser login without changing existing tokens."""
    self._pending_login = None
    self._login_generation += 1

complete_login async

Validate and consume a browser callback, then exchange its code once.

Every completion attempt consumes the pending state, including an invalid callback or a failed exchange. Start a new attempt after any failure; an uncertain token request is never retried automatically. The callback, authorization code and PKCE verifier are not logged. Existing token callbacks and automatic refresh work as after login().

Source code in cookidoo_api/cookidoo.py
async def complete_login(self, authorization_response: str) -> None:
    """Validate and consume a browser callback, then exchange its code once.

    Every completion attempt consumes the pending state, including an
    invalid callback or a failed exchange. Start a new attempt after any
    failure; an uncertain token request is never retried automatically.
    The callback, authorization code and PKCE verifier are not logged.
    Existing token callbacks and automatic refresh work as after login().
    """
    pending = self._pending_login
    self.cancel_login()
    generation = self._login_generation
    if pending is None:
        raise CookidooAuthException("No browser login is pending.")
    if time.monotonic() >= pending.expires_at:
        raise CookidooAuthException("The browser login has expired.")
    if (pending.client_id, pending.redirect_uri) != (
        self._cfg.client_id,
        self._cfg.redirect_uri,
    ):
        raise CookidooAuthException("OAuth client configuration changed.")
    try:
        received = urlparse(authorization_response)
        expected = urlparse(pending.redirect_uri)
    except ValueError:
        raise CookidooAuthException("Invalid authorization callback.") from None
    if received.fragment or (
        received.scheme,
        received.netloc,
        received.path,
    ) != (expected.scheme, expected.netloc, expected.path):
        raise CookidooAuthException("Unexpected authorization callback target.")
    query = parse_qs(received.query, keep_blank_values=True)
    states = query.get("state", [])
    if len(states) != 1 or not secrets.compare_digest(
        states[0].encode(), pending.state.encode()
    ):
        raise CookidooAuthException("OAuth state mismatch.")
    if "error" in query:
        raise CookidooAuthException("Browser authorization was declined.")
    codes = query.get("code", [])
    if len(codes) != 1 or not codes[0]:
        raise CookidooAuthException("No unique authorization code returned.")
    try:
        payload = await self._authorization_code_tokens(
            pending.token_endpoint, codes[0], pending.verifier
        )
        self._assert_login_generation(generation)
        self._apply_tokens(payload)
    except TimeoutError as e:
        raise CookidooRequestException("Authorization exchange timed out.") from e
    except ClientError as e:
        raise CookidooRequestException("Authorization exchange failed.") from e
    except CookidooAuthException:
        raise CookidooAuthException(
            "Authorization exchange was rejected."
        ) from None

login async

Perform an OAuth2 authorization-code + PKCE login.

Signs in with the configured email/password against the CIAM identity provider, exchanges the resulting code for an access/refresh token, and authenticates all subsequent API calls via a Bearer header:

  1. discover the OIDC endpoints
  2. open the authorize endpoint to reach the CIAM login form
  3. POST the credentials to the CIAM login service
  4. capture the code from the redirect to the app scheme
  5. exchange the code for tokens (public client, PKCE, no secret)

The login redirects still rely on the session cookie jar, so a CookieJar(unsafe=True) session is required. The login requests carry a browser-like User-Agent (request-scoped only) since the flow is served behind Cloudflare.

Raises:

Type Description
CookidooConfigException

If the OAuth2 client id or redirect uri was overridden with an empty value.

CookidooRequestException

If the request fails.

CookidooParseException

If the login page cannot be parsed.

CookidooAuthException

If the login fails due to invalid credentials.

Source code in cookidoo_api/cookidoo.py
async def login(self) -> None:
    """Perform an OAuth2 authorization-code + PKCE login.

    Signs in with the configured email/password against the CIAM identity
    provider, exchanges the resulting code for an access/refresh token, and
    authenticates all subsequent API calls via a ``Bearer`` header:

    1. discover the OIDC endpoints
    2. open the authorize endpoint to reach the CIAM login form
    3. POST the credentials to the CIAM login service
    4. capture the ``code`` from the redirect to the app scheme
    5. exchange the code for tokens (public client, PKCE, no secret)

    The login redirects still rely on the session cookie jar, so a
    ``CookieJar(unsafe=True)`` session is required. The login requests carry
    a browser-like ``User-Agent`` (request-scoped only) since the flow is
    served behind Cloudflare.

    Raises
    ------
    CookidooConfigException
        If the OAuth2 client id or redirect uri was overridden with an
        empty value.
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the login page cannot be parsed.
    CookidooAuthException
        If the login fails due to invalid credentials.

    """
    self._assert_oauth_client()
    self.cancel_login()
    try:
        oidc = await self._discovery()
        verifier, challenge = self._pkce_pair()
        state = secrets.token_urlsafe(12)
        language = self._cfg.localization.language
        params = {
            "response_type": "code",
            "client_id": self._cfg.client_id,
            "redirect_uri": self._cfg.redirect_uri,
            "market": self._cfg.localization.country_code,
            "scope": OAUTH_SCOPE,
            "state": state,
            "code_challenge": challenge,
            "code_challenge_method": "S256",
            "ui_locales": language,
        }

        # Step 2: reach the CIAM login form (follows redirects, sets cookies)
        async with self._session.get(
            URL(oidc["authorization_endpoint"]),
            params=params,
            allow_redirects=True,
            headers=LOGIN_HEADERS,
        ) as resp:
            self._check_login_page_status(resp.status)
            login_html = await resp.text()

        # Step 3: submit credentials, Step 4: capture the authorization code
        request_id = self._extract_request_id(login_html)
        code = await self._submit_credentials(request_id, state)

        # Step 5: exchange the code for tokens, which marks us logged in
        await self._exchange_code(oidc["token_endpoint"], code, verifier)

    except (CookidooAuthException, CookidooParseException):
        raise
    except TimeoutError as e:
        _LOGGER.debug("Exception: Login failed:\n %s", traceback.format_exc())
        raise CookidooRequestException(
            "Authentication failed due to connection timeout."
        ) from e
    except ClientError as e:
        _LOGGER.debug("Exception: Login failed:\n %s", traceback.format_exc())
        raise CookidooRequestException(
            "Authentication failed due to request exception."
        ) from e

refresh async

Refresh the access token using the stored refresh token.

Raises:

Type Description
CookidooAuthException

If there is no refresh token or the refresh is rejected.

CookidooConfigException

If the OAuth2 client id or redirect uri was overridden with an empty value.

CookidooRequestException

If the discovery or token request fails or times out.

Source code in cookidoo_api/cookidoo.py
async def refresh(self) -> None:
    """Refresh the access token using the stored refresh token.

    Raises
    ------
    CookidooAuthException
        If there is no refresh token or the refresh is rejected.
    CookidooConfigException
        If the OAuth2 client id or redirect uri was overridden with an
        empty value.
    CookidooRequestException
        If the discovery or token request fails or times out.

    """
    if self._refresh_token is None:
        raise CookidooAuthException("Cannot refresh: no refresh token available.")
    self._assert_oauth_client()
    try:
        # Inside the try: the discovery request can fail the same way the
        # token request can, and callers only expect CookidooException.
        oidc = await self._discovery()
        async with self._session.post(
            URL(oidc["token_endpoint"]),
            data={
                "grant_type": "refresh_token",
                "refresh_token": self._refresh_token,
                "client_id": self._cfg.client_id,
            },
            headers=LOGIN_HEADERS,
        ) as resp:
            if resp.status != HTTPStatus.OK:
                raise CookidooAuthException(
                    f"Token refresh failed (status {resp.status})."
                )
            payload = cast(dict[str, object], await resp.json())
    except TimeoutError as e:
        _LOGGER.debug(
            "Exception: Token refresh failed:\n %s", traceback.format_exc()
        )
        raise CookidooRequestException(
            "Token refresh failed due to connection timeout."
        ) from e
    except ClientError as e:
        _LOGGER.debug(
            "Exception: Token refresh failed:\n %s", traceback.format_exc()
        )
        raise CookidooRequestException(
            "Token refresh failed due to request exception."
        ) from e
    self._apply_tokens(payload)

save_token

Save the OAuth2 tokens to a file for later reuse.

Parameters:

Name Type Description Default
path str | Path

Path to the file where the tokens will be saved.

required
Source code in cookidoo_api/cookidoo.py
def save_token(self, path: str | Path) -> None:
    """Save the OAuth2 tokens to a file for later reuse.

    Parameters
    ----------
    path
        Path to the file where the tokens will be saved.

    """
    if (auth_data := self.auth_data) is None:
        raise CookidooConfigException("Cannot save token: not logged in.")
    Path(path).write_text(json.dumps(vars(auth_data)), encoding="utf-8")

load_token

Restore the OAuth2 tokens from a file saved with :meth:save_token.

Parameters:

Name Type Description Default
path str | Path

Path to the file containing the saved tokens.

required

Raises:

Type Description
CookidooConfigException

If the token file cannot be read or parsed.

Source code in cookidoo_api/cookidoo.py
def load_token(self, path: str | Path) -> None:
    """Restore the OAuth2 tokens from a file saved with :meth:`save_token`.

    Parameters
    ----------
    path
        Path to the file containing the saved tokens.

    Raises
    ------
    CookidooConfigException
        If the token file cannot be read or parsed.

    """
    try:
        data = json.loads(Path(path).read_text(encoding="utf-8"))
        self.apply_auth_data(CookidooAuthData(**data))
    except (OSError, json.JSONDecodeError, TypeError) as e:
        raise CookidooConfigException(f"Cannot load token from {path}.") from e

get_user_info async

Get user info.

Returns:

Type Description
CookidooUserInfo

The user info

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def get_user_info(
    self,
) -> CookidooUserInfo:
    """Get user info.

    Returns
    -------
    CookidooUserInfo
        The user info

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """

    await self._ensure_endpoints()
    url = self.api_endpoint / self._path(
        "community-profile:user-private-profile"
    ).format(**self._cfg.localization.__dict__)
    result = self._ensure_mapping(
        await self._request_json("get", url, "loading user info"),
        "loading user info",
    )
    return self._parse_result(
        "loading user info",
        lambda: cookidoo_user_info_from_json(cast(CommunityProfileJSON, result)),
    )

get_active_subscription async

Get active subscription if any.

Returns:

Type Description
CookidooSubscription

The active subscription

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def get_active_subscription(
    self,
) -> CookidooSubscription | None:
    """Get active subscription if any.

    Returns
    -------
    CookidooSubscription
        The active subscription

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """

    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("ownership:subscriptions").format(
        **self._cfg.localization.__dict__
    )
    subscriptions = self._ensure_sequence(
        await self._request_json("get", url, "loading active subscription"),
        "loading active subscription",
    )
    try:
        if subscription := next(
            (
                subscription
                for subscription in subscriptions
                if isinstance(subscription, Mapping) and subscription["active"]
            ),
            None,
        ):
            return self._parse_result(
                "loading active subscription",
                lambda: cookidoo_subscription_from_json(
                    cast(SubscriptionJSON, subscription)
                ),
            )
    except KeyError as e:
        raise CookidooParseException(
            "Loading active subscription failed during parsing of request response."
        ) from e
    return None

get_devices async

Get the Thermomix appliances paired to the account.

Returns:

Type Description
list[CookidooDevice]

The paired appliances, identified by machine type (e.g. TM7). An empty list when no appliance is paired.

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def get_devices(self) -> list[CookidooDevice]:
    """Get the Thermomix appliances paired to the account.

    Returns
    -------
    list[CookidooDevice]
        The paired appliances, identified by machine type (e.g. ``TM7``).
        An empty list when no appliance is paired.

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path(
        "customer-devices:thermomix-versions"
    ).format(**self._cfg.localization.__dict__)
    result = await self._request_json("get", url, "loading devices")
    if result is None:
        # An account without a paired appliance gets a 204 No Content.
        return []
    models = self._ensure_sequence(result, "loading devices")
    return self._parse_result(
        "loading devices",
        lambda: [cookidoo_device_from_json(cast(str, model)) for model in models],
    )

get_monitored_device_ids async

Get the appliance IDs currently available for remote monitoring.

Note this is distinct from :meth:get_devices (all paired appliances): an appliance only appears here while it is online/reachable for monitoring, and the identifier is the opaque remote-monitoring device id.

Returns:

Type Description
list[str]

The remote-monitoring device ids (empty when none are available).

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def get_monitored_device_ids(self) -> list[str]:
    """Get the appliance IDs currently available for remote monitoring.

    Note this is distinct from :meth:`get_devices` (all paired appliances):
    an appliance only appears here while it is online/reachable for
    monitoring, and the identifier is the opaque remote-monitoring device id.

    Returns
    -------
    list[str]
        The remote-monitoring device ids (empty when none are available).

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    links = await self._resolve_rmi_links()
    url = links.get(RMI_DEVICES)
    if url is None:
        raise CookidooParseException("rmi:devices link missing.")
    devices = self._ensure_sequence(
        await self._request_json(
            "get", URL(url.split("{")[0]), "loading monitored devices"
        ),
        "loading monitored devices",
    )
    return self._parse_result(
        "loading monitored devices",
        lambda: [
            cast(str, cast(Mapping[str, object], device)["deviceId"])
            for device in devices
        ],
    )

register_push_token async

Register a push token to receive remote-monitoring cook-state updates.

Appliance state is delivered as a Firebase Cloud Messaging data message to the registered token; obtaining the token and receiving the messages is the caller's responsibility. Decode received payloads with :func:cookidoo_api.cooking_activity_from_push.

Parameters:

Name Type Description Default
push_token str

The FCM registration token to deliver updates to.

required
mobile_app_id str

A stable per-installation identifier for this client.

required

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def register_push_token(self, push_token: str, mobile_app_id: str) -> None:
    """Register a push token to receive remote-monitoring cook-state updates.

    Appliance state is delivered as a Firebase Cloud Messaging data message
    to the registered token; obtaining the token and receiving the messages
    is the caller's responsibility. Decode received payloads with
    :func:`cookidoo_api.cooking_activity_from_push`.

    Parameters
    ----------
    push_token
        The FCM registration token to deliver updates to.
    mobile_app_id
        A stable per-installation identifier for this client.

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    links = await self._resolve_rmi_links()
    url = links.get(RMI_REGISTER_TOKEN)
    if url is None:
        raise CookidooParseException("rmi:register-token link missing.")
    await self._request_json(
        "post",
        URL(url),
        "registering push token",
        json={
            "token": push_token,
            "bundleId": PUSH_BUNDLE_ID,
            "platform": PUSH_PLATFORM,
            "mobileAppId": mobile_app_id,
        },
        headers={"rmi-api-version": RMI_API_VERSION},
        parse_response=False,
    )

unregister_push_token async

Unregister a previously registered push token.

Parameters:

Name Type Description Default
push_token str

The FCM registration token to stop delivering updates to.

required

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def unregister_push_token(self, push_token: str) -> None:
    """Unregister a previously registered push token.

    Parameters
    ----------
    push_token
        The FCM registration token to stop delivering updates to.

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    links = await self._resolve_rmi_links()
    url = links.get(RMI_UNREGISTER)
    if url is None:
        raise CookidooParseException("rmi:unregister link missing.")
    await self._request_json(
        "delete",
        URL(url),
        "unregistering push token",
        json={"tokens": [push_token]},
        headers={"rmi-api-version": RMI_API_VERSION},
        parse_response=False,
    )

get_recipe_details async

Get recipe details.

Parameters:

Name Type Description Default
id str

The id of the recipe

required

Returns:

Type Description
CookidooShoppingRecipeDetails

The recipe details

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def get_recipe_details(self, id: str) -> CookidooShoppingRecipeDetails:
    """Get recipe details.

    Parameters
    ----------
    id
        The id of the recipe

    Returns
    -------
    CookidooShoppingRecipeDetails
        The recipe details

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """

    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("recipe:details").format(
        **self._cfg.localization.__dict__, id=id
    )
    result = self._ensure_mapping(
        await self._request_json("get", url, "loading recipe details"),
        "loading recipe details",
    )
    return self._parse_result(
        "loading recipe details",
        lambda: cookidoo_recipe_details_from_json(
            cast(RecipeDetailsJSON, result),
            self._cfg.localization,
        ),
    )

search_recipes async

Search recipes in Cookidoo (GET).

Uses the same API base as the rest of the client (api_endpoint): {api_endpoint}/search/{locale}

Parameters:

Name Type Description Default
query str | None

Optional search query (e.g. "chicken", "pasta").

None
locale str | None

Locale for the search path (e.g. "es", "en", "de"). Defaults to the first part of the configured language (e.g. "de-CH" -> "de").

None
accessories str | list[str] | None

Optional comma-separated accessory filters (e.g. "includingFriend,includingBladeCover,includingBladeCoverWithPeeler,includingCutter,includingSensor").

None
languages str | list[str] | None

Optional comma-separated language codes (e.g. "en,es").

None
categories str | list[str] | None

Optional comma-separated category IDs.

None
countries str | list[str] | None

Optional comma-separated country codes (e.g. "ar").

None
ingredients str | list[str] | None

Optional comma-separated ingredients.

None
exclude_ingredients str | list[str] | None

Optional comma-separated excluded ingredients.

None
tags str | list[str] | None

Optional comma-separated tags.

None
ratings str | list[str] | None

Optional comma-separated ratings (e.g. "5,4").

None
difficulty str | None

Optional difficulty (e.g. "easy", "medium", "hard").

None
preparation_time int | None

Optional preparation time in seconds.

None
total_time int | None

Optional total time in seconds.

None
portions int | None

Optional portions count.

None
page int | None

Optional page number (API-dependent, often 0- or 1-based).

None
page_size int | None

Optional page size (API-dependent; common keys: pageSize).

None
tmv ThermomixMachineType | str | list[ThermomixMachineType | str] | None

Optional Thermomix machine version. Use ThermomixMachineType (e.g. ThermomixMachineType.TM7) or a string ("TM7", "TM6", "TM5").

None

Returns:

Type Description
CookidooSearchResult

Search result with recipes and total count.

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore.

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def search_recipes(
    self,
    query: str | None = None,
    *,
    locale: str | None = None,
    accessories: str | list[str] | None = None,
    languages: str | list[str] | None = None,
    categories: str | list[str] | None = None,
    countries: str | list[str] | None = None,
    ingredients: str | list[str] | None = None,
    exclude_ingredients: str | list[str] | None = None,
    tags: str | list[str] | None = None,
    ratings: str | list[str] | None = None,
    difficulty: str | None = None,
    preparation_time: int | None = None,
    total_time: int | None = None,
    portions: int | None = None,
    page: int | None = None,
    page_size: int | None = None,
    tmv: ThermomixMachineType
    | str
    | list[ThermomixMachineType | str]
    | None = None,
) -> CookidooSearchResult:
    """Search recipes in Cookidoo (GET).

    Uses the same API base as the rest of the client (api_endpoint):
    {api_endpoint}/search/{locale}

    Parameters
    ----------
    query
        Optional search query (e.g. "chicken", "pasta").
    locale
        Locale for the search path (e.g. "es", "en", "de").
        Defaults to the first part of the configured language (e.g. "de-CH" -> "de").
    accessories
        Optional comma-separated accessory filters
        (e.g. "includingFriend,includingBladeCover,includingBladeCoverWithPeeler,includingCutter,includingSensor").
    languages
        Optional comma-separated language codes (e.g. "en,es").
    categories
        Optional comma-separated category IDs.
    countries
        Optional comma-separated country codes (e.g. "ar").
    ingredients
        Optional comma-separated ingredients.
    exclude_ingredients
        Optional comma-separated excluded ingredients.
    tags
        Optional comma-separated tags.
    ratings
        Optional comma-separated ratings (e.g. "5,4").
    difficulty
        Optional difficulty (e.g. "easy", "medium", "hard").
    preparation_time
        Optional preparation time in seconds.
    total_time
        Optional total time in seconds.
    portions
        Optional portions count.
    page
        Optional page number (API-dependent, often 0- or 1-based).
    page_size
        Optional page size (API-dependent; common keys: pageSize).
    tmv
        Optional Thermomix machine version. Use ``ThermomixMachineType``
        (e.g. ``ThermomixMachineType.TM7``) or a string ("TM7", "TM6", "TM5").

    Returns
    -------
    CookidooSearchResult
        Search result with recipes and total count.

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore.
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    if locale is None:
        locale = self._cfg.localization.language.split("-")[0]
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("search:home").format(locale=locale)
    params: dict[str, str] = {}
    if query is not None:
        params["query"] = query
    if accessories is not None and (
        normalized := normalize_list_param(accessories)
    ):
        params["accessories"] = normalized
    if languages is not None and (normalized := normalize_list_param(languages)):
        params["languages"] = normalized
    if categories is not None and (normalized := normalize_list_param(categories)):
        params["categories"] = normalized
    if countries is not None and (normalized := normalize_list_param(countries)):
        params["countries"] = normalized
    if ingredients is not None and (
        normalized := normalize_list_param(ingredients)
    ):
        params["ingredients"] = normalized
    if exclude_ingredients is not None and (
        normalized := normalize_list_param(exclude_ingredients)
    ):
        params["excludeIngredients"] = normalized
    if tags is not None and (normalized := normalize_list_param(tags)):
        params["tags"] = normalized
    if ratings is not None and (normalized := normalize_list_param(ratings)):
        params["ratings"] = normalized
    if difficulty is not None:
        params["difficulty"] = difficulty
    if preparation_time is not None:
        params["preparationTime"] = str(preparation_time)
    if total_time is not None:
        params["totalTime"] = str(total_time)
    if portions is not None:
        params["portions"] = str(portions)
    if page is not None:
        params["page"] = str(page)
    if page_size is not None:
        params["pageSize"] = str(page_size)
    if tmv is not None and (normalized := normalize_tmv_param(tmv)):
        params["tmv"] = normalized
    result = await self._request_json("get", url, "search recipes", params=params)
    if result is None:
        return CookidooSearchResult(recipes=[], total=0)
    if not isinstance(result, dict):
        raise CookidooParseException(
            "Search recipes failed during parsing of request response."
        )
    return cookidoo_search_result_from_json(
        cast(SearchResultJSON, result), self._cfg.localization
    )

get_custom_recipe async

Get custom recipe.

Parameters:

Name Type Description Default
id str

The id of the custom recipe

required

Returns:

Type Description
CookidooCustomRecipe

The custom recipe

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def get_custom_recipe(self, id: str) -> CookidooCustomRecipe:
    """Get custom recipe.

    Parameters
    ----------
    id
        The id of the custom recipe

    Returns
    -------
    CookidooCustomRecipe
        The custom recipe

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """

    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("customer-recipes:recipe-details").format(
        **self._cfg.localization.__dict__, id=id
    )
    result = self._ensure_mapping(
        await self._request_json(
            "get",
            url,
            "loading custom recipe",
            headers={"ACCEPT": CUSTOM_RECIPES_PATH_ACCEPT},
        ),
        "loading custom recipe",
    )
    return self._parse_result(
        "loading custom recipe",
        lambda: cookidoo_custom_recipe_from_json(
            cast(CustomRecipeJSON, result),
            self._cfg.localization,
        ),
    )

list_custom_recipes async

List custom recipes.

Source code in cookidoo_api/cookidoo.py
async def list_custom_recipes(self) -> list[CookidooCustomRecipe]:
    """List custom recipes."""
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("customer-recipes:recipe-create").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json(
            "get",
            url,
            "listing custom recipes",
            headers={"ACCEPT": CUSTOM_RECIPES_PATH_ACCEPT},
        ),
        "listing custom recipes",
    )
    if not isinstance(result.get("items"), list):
        raise CookidooParseException(
            "Listing custom recipes failed during parsing of request response."
        )

    custom_recipes = cast(CustomRecipesJSON, result)
    return self._parse_result(
        "listing custom recipes",
        lambda: [
            cookidoo_custom_recipe_from_json(recipe, self._cfg.localization)
            for recipe in custom_recipes["items"]
        ],
    )

add_custom_recipe_from async

Add custom recipe.

Parameters:

Name Type Description Default
recipeId str

The base recipe to copy

required
servingSize int

The serving size of the custom recipe

required

Returns:

Type Description
CookidooCustomRecipe

The added custom recipe

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def add_custom_recipe_from(
    self, recipeId: str, servingSize: int
) -> CookidooCustomRecipe:
    """Add custom recipe.

    Parameters
    ----------
    recipeId
        The base recipe to copy
    servingSize
        The serving size of the custom recipe

    Returns
    -------
    CookidooCustomRecipe
        The added custom recipe

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    await self._ensure_endpoints()
    json_data = {
        "recipeUrl": str(
            self.api_endpoint
            / self._path("recipe:details").format(
                **self._cfg.localization.__dict__, id=recipeId
            )
        ),
        "servingSize": servingSize,
    }
    url = self.api_endpoint / self._path("customer-recipes:recipe-create").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json("post", url, "add custom recipe", json=json_data),
        "add custom recipe",
    )
    return self._parse_result(
        "add custom recipe",
        lambda: cookidoo_custom_recipe_from_json(
            cast(CustomRecipeJSON, result),
            self._cfg.localization,
        ),
    )

remove_custom_recipe async

Remove custom recipe.

Parameters:

Name Type Description Default
custom_recipe_id str

The custom recipe id to remove

required

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def remove_custom_recipe(
    self,
    custom_recipe_id: str,
) -> None:
    """Remove custom recipe.

    Parameters
    ----------
    custom_recipe_id
        The custom recipe id to remove

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("customer-recipes:recipe-details").format(
        **self._cfg.localization.__dict__, id=custom_recipe_id
    )
    await self._request_json(
        "delete", url, "remove custom recipe", parse_response=False
    )

create_custom_recipe async

Create, populate, and return a custom recipe.

Creation uses a stub POST followed by a PATCH with the full payload and a final GET reload. If the populate or reload step fails after the stub was created, the orphaned recipe id is attached to the raised exception via Exception.add_note.

Raises:

Type Description
ValueError

If the recipe input fails local validation.

CookidooAuthException

When the access token is not valid anymore.

CookidooRequestException

If a request fails after the stub may already exist server-side.

CookidooParseException

If a response cannot be parsed after the stub may already exist.

Source code in cookidoo_api/cookidoo.py
async def create_custom_recipe(
    self, recipe: CookidooCreateCustomRecipe
) -> CookidooCustomRecipe:
    """Create, populate, and return a custom recipe.

    Creation uses a stub ``POST`` followed by a ``PATCH`` with the full
    payload and a final ``GET`` reload. If the populate or reload step
    fails after the stub was created, the orphaned recipe id is attached
    to the raised exception via ``Exception.add_note``.

    Raises
    ------
    ValueError
        If the recipe input fails local validation.
    CookidooAuthException
        When the access token is not valid anymore.
    CookidooRequestException
        If a request fails after the stub may already exist server-side.
    CookidooParseException
        If a response cannot be parsed after the stub may already exist.

    """
    self._validate_custom_recipe_image(recipe.image)
    recipe_machines: Sequence[ThermomixMachineType | str] = recipe.tools or [
        ThermomixMachineType.TM7
    ]

    payload = self._build_custom_recipe_payload(
        name=recipe.name,
        ingredients=recipe.ingredients,
        steps=recipe.instructions,
        servings=recipe.serving_size,
        active_time=recipe.active_time,
        total_time=recipe.total_time,
        hints=recipe.hints,
        machine_types=recipe_machines,
        unit_text=recipe.unit_text,
        image=recipe.image,
        image_owned_by_user=recipe.image is not None,
        work_status=recipe.work_status,
        requires_annotations_check=recipe.requires_annotations_check,
    )

    await self._ensure_endpoints()
    url_create = self.api_endpoint / self._path(
        "customer-recipes:recipe-create"
    ).format(**self._cfg.localization.__dict__)
    create_json: CreateCustomRecipeJSON = {"recipeName": recipe.name}
    created_recipe = self._ensure_mapping(
        await self._request_json(
            "post",
            url_create,
            "create custom recipe",
            json=create_json,
            headers={"CONTENT-TYPE": "application/json"},
        ),
        "create custom recipe",
    )

    recipe_id = created_recipe.get("recipeId")
    if not isinstance(recipe_id, str) or not recipe_id:
        raise CookidooParseException("No recipe ID returned from creation.")

    try:
        await self._patch_custom_recipe(recipe_id, payload, "update custom recipe")
        return await self.get_custom_recipe(recipe_id)
    except CookidooException as e:
        _LOGGER.warning(
            "Custom recipe %s was created but could not be populated", recipe_id
        )
        e.add_note(f"Orphaned custom recipe stub id: {recipe_id}")
        raise

update_custom_recipe async

Update selected fields and return the refreshed custom recipe.

Source code in cookidoo_api/cookidoo.py
async def update_custom_recipe(
    self, recipe_id: str, recipe: CookidooUpdateCustomRecipe
) -> CookidooCustomRecipe:
    """Update selected fields and return the refreshed custom recipe."""
    self._validate_custom_recipe_image(recipe.image)
    existing = await self.get_custom_recipe(recipe_id)
    if (
        recipe.image is None
        and existing.image is not None
        and existing.image_owned_by_user
        and self._custom_recipe_image_for_payload(existing.image) is None
    ):
        raise ValueError("Cannot preserve the existing custom recipe image.")
    if recipe.image_owned_by_user is not None:
        image_owned_by_user = recipe.image_owned_by_user
    elif recipe.image is not None:
        image_owned_by_user = True
    else:
        image_owned_by_user = existing.image_owned_by_user
    payload = self._build_custom_recipe_payload(
        name=recipe.name if recipe.name is not None else existing.name,
        ingredients=(
            recipe.ingredients
            if recipe.ingredients is not None
            else existing.ingredients
        ),
        steps=(
            recipe.instructions
            if recipe.instructions is not None
            else existing.instructions
        ),
        servings=(
            recipe.serving_size
            if recipe.serving_size is not None
            else existing.serving_size
        ),
        active_time=(
            recipe.active_time
            if recipe.active_time is not None
            else existing.active_time
        ),
        total_time=(
            recipe.total_time
            if recipe.total_time is not None
            else existing.total_time
        ),
        hints=recipe.hints if recipe.hints is not None else existing.hints,
        machine_types=recipe.tools if recipe.tools is not None else existing.tools,
        unit_text=(
            recipe.unit_text if recipe.unit_text is not None else existing.unit_text
        ),
        image=recipe.image if recipe.image is not None else existing.image,
        image_owned_by_user=image_owned_by_user,
        work_status=(
            recipe.work_status
            if recipe.work_status is not None
            else existing.work_status
        ),
        requires_annotations_check=(
            recipe.requires_annotations_check
            if recipe.requires_annotations_check is not None
            else existing.requires_annotations_check
        ),
    )
    await self._patch_custom_recipe(recipe_id, payload, "update custom recipe")
    return await self.get_custom_recipe(recipe_id)

get_shopping_list_recipes async

Get recipes.

Returns:

Type Description
list[CookidooShoppingRecipe]

The list of the recipes

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def get_shopping_list_recipes(
    self,
) -> list[CookidooShoppingRecipe]:
    """Get recipes.

    Returns
    -------
    list[CookidooShoppingRecipe]
        The list of the recipes

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """

    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("pantry:home").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json("get", url, "loading recipes"),
        "loading recipes",
    )
    return self._parse_result(
        "loading recipes",
        lambda: [
            cookidoo_recipe_from_json(
                cast(RecipeJSON, recipe), self._cfg.localization
            )
            for recipe in [
                *cast(Sequence[object], result["recipes"]),
                *cast(Sequence[object], result["customerRecipes"]),
            ]
        ],
    )

get_ingredient_items async

Get ingredient items.

Returns:

Type Description
list[CookidooIngredientItem]

The list of the ingredient items

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def get_ingredient_items(
    self,
) -> list[CookidooIngredientItem]:
    """Get ingredient items.

    Returns
    -------
    list[CookidooIngredientItem]
        The list of the ingredient items

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """

    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("pantry:home").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json("get", url, "loading ingredient items"),
        "loading ingredient items",
    )
    return self._parse_result(
        "loading ingredient items",
        lambda: [
            cookidoo_ingredient_item_from_json(cast(ItemJSON, ingredient))
            for recipe in [
                *cast(Sequence[Mapping[str, object]], result["recipes"]),
                *cast(Sequence[Mapping[str, object]], result["customerRecipes"]),
            ]
            for ingredient in cast(
                Sequence[object], recipe["recipeIngredientGroups"]
            )
        ],
    )

add_ingredient_items_for_recipes async

Add ingredient items for recipes.

Parameters:

Name Type Description Default
recipe_ids list[str]

The recipe ids for the ingredient items to add to the shopping list

required

Returns:

Type Description
list[CookidooIngredientItem]

The list of the added ingredient items

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def add_ingredient_items_for_recipes(
    self,
    recipe_ids: list[str],
) -> list[CookidooIngredientItem]:
    """Add ingredient items for recipes.

    Parameters
    ----------
    recipe_ids
        The recipe ids for the ingredient items to add to the shopping list

    Returns
    -------
    list[CookidooIngredientItem]
        The list of the added ingredient items

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    json_data = {"recipeIDs": recipe_ids}
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("pantry:recipe-ingredients").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json(
            "post", url, "add ingredient items for recipes", json=json_data
        ),
        "add ingredient items for recipes",
    )
    return self._parse_result(
        "loading added ingredient items",
        lambda: [
            cookidoo_ingredient_item_from_json(cast(ItemJSON, ingredient))
            for recipe in cast(Sequence[Mapping[str, object]], result["data"])
            for ingredient in cast(
                Sequence[object], recipe["recipeIngredientGroups"]
            )
        ],
    )

remove_ingredient_items_for_recipes async

Remove ingredient items for recipes.

Parameters:

Name Type Description Default
recipe_ids list[str]

The recipe ids for the ingredient items to remove to the shopping list

required

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def remove_ingredient_items_for_recipes(
    self,
    recipe_ids: list[str],
) -> None:
    """Remove ingredient items for recipes.

    Parameters
    ----------
    recipe_ids
        The recipe ids for the ingredient items to remove to the shopping list

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    json_data = {"recipeIDs": recipe_ids}
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("pantry:remove-recipe").format(
        **self._cfg.localization.__dict__
    )
    await self._request_json(
        "post",
        url,
        "remove ingredient items for recipes",
        json=json_data,
        parse_response=False,
    )

edit_ingredient_items_ownership async

Edit ownership ingredient items.

Parameters:

Name Type Description Default
ingredient_items list[CookidooIngredientItem]

The ingredient items to change the the is_owned value for

required

Returns:

Type Description
list[CookidooIngredientItem]

The list of the edited ingredient items

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def edit_ingredient_items_ownership(
    self,
    ingredient_items: list[CookidooIngredientItem],
) -> list[CookidooIngredientItem]:
    """Edit ownership ingredient items.

    Parameters
    ----------
    ingredient_items
        The ingredient items to change the the `is_owned` value for

    Returns
    -------
    list[CookidooIngredientItem]
        The list of the edited ingredient items

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    json_data = {
        "ingredients": [
            {
                "id": ingredient_item.id,
                "isOwned": ingredient_item.is_owned,
                "ownedTimestamp": int(time.time()),
            }
            for ingredient_item in ingredient_items
        ]
    }
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path(
        "pantry:edit-ingredients-ownership"
    ).format(**self._cfg.localization.__dict__)
    result = self._ensure_mapping(
        await self._request_json(
            "post", url, "edit ingredient items ownership", json=json_data
        ),
        "edit ingredient items ownership",
    )
    return self._parse_result(
        "loading edited ingredient items",
        lambda: [
            cookidoo_ingredient_item_from_json(cast(ItemJSON, ingredient))
            for ingredient in cast(Sequence[object], result["data"])
        ],
    )

add_ingredient_items_for_custom_recipes async

Add ingredient items for custom recipes.

Parameters:

Name Type Description Default
recipe_ids list[str]

The recipe ids for the ingredient items to add to the shopping list

required

Returns:

Type Description
list[CookidooIngredientItem]

The list of the added ingredient items

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def add_ingredient_items_for_custom_recipes(
    self,
    recipe_ids: list[str],
) -> list[CookidooIngredientItem]:
    """Add ingredient items for custom recipes.

    Parameters
    ----------
    recipe_ids
        The recipe ids for the ingredient items to add to the shopping list

    Returns
    -------
    list[CookidooIngredientItem]
        The list of the added ingredient items

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    json_data = {
        "recipeIDs": [
            {"id": recipe_id, "source": "CUSTOMER"} for recipe_id in recipe_ids
        ]
    }
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("pantry:recipe-ingredients").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json(
            "post", url, "add ingredient items for custom recipes", json=json_data
        ),
        "add ingredient items for custom recipes",
    )
    return self._parse_result(
        "loading added ingredient items",
        lambda: [
            cookidoo_ingredient_item_from_json(cast(ItemJSON, ingredient))
            for recipe in cast(Sequence[Mapping[str, object]], result["data"])
            for ingredient in cast(
                Sequence[object], recipe["recipeIngredientGroups"]
            )
        ],
    )

remove_ingredient_items_for_custom_recipes async

Remove ingredient items for custom recipes.

Parameters:

Name Type Description Default
recipe_ids list[str]

The custom recipe ids for the ingredient items to remove to the shopping list

required

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def remove_ingredient_items_for_custom_recipes(
    self,
    recipe_ids: list[str],
) -> None:
    """Remove ingredient items for custom recipes.

    Parameters
    ----------
    recipe_ids
        The custom recipe ids for the ingredient items to remove to the shopping list

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    json_data = {"recipeIDs": recipe_ids}
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("pantry:remove-recipe").format(
        **self._cfg.localization.__dict__
    )
    await self._request_json(
        "post",
        url,
        "remove ingredient items for custom recipes",
        json=json_data,
        parse_response=False,
    )

get_additional_items async

Get additional items.

Returns:

Type Description
list[CookidooAdditionalItem]

The list of the additional items

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def get_additional_items(
    self,
) -> list[CookidooAdditionalItem]:
    """Get additional items.

    Returns
    -------
    list[CookidooAdditionalItem]
        The list of the additional items

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """

    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("pantry:home").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json("get", url, "loading additional items"),
        "loading additional items",
    )
    return self._parse_result(
        "loading additional items",
        lambda: [
            cookidoo_additional_item_from_json(
                cast(AdditionalItemJSON, additional_item)
            )
            for additional_item in cast(Sequence[object], result["additionalItems"])
        ],
    )

add_additional_items async

Create additional items.

Parameters:

Name Type Description Default
additional_item_names list[str]

The additional item names to create, only the label can be set, as the default state is_owned=false is forced (chain with immediate update call for work-around)

required

Returns:

Type Description
list[CookidooAdditionalItem]

The list of the added additional items

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def add_additional_items(
    self,
    additional_item_names: list[str],
) -> list[CookidooAdditionalItem]:
    """Create additional items.

    Parameters
    ----------
    additional_item_names
        The additional item names to create, only the label can be set, as the default state `is_owned=false` is forced (chain with immediate update call for work-around)

    Returns
    -------
    list[CookidooAdditionalItem]
        The list of the added additional items

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    json_data = {"itemsValue": additional_item_names}
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("pantry:add-additional-items-v2").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json(
            "post", url, "add additional items", json=json_data
        ),
        "add additional items",
    )
    return self._parse_result(
        "loading added additional items",
        lambda: [
            cookidoo_additional_item_from_json(
                cast(AdditionalItemJSON, additional_item)
            )
            for additional_item in cast(Sequence[object], result["data"])
        ],
    )

edit_additional_items async

Edit additional items.

Parameters:

Name Type Description Default
additional_items list[CookidooAdditionalItem]

The additional items to change the the name value for

required

Returns:

Type Description
list[CookidooAdditionalItem]

The list of the edited additional items

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def edit_additional_items(
    self,
    additional_items: list[CookidooAdditionalItem],
) -> list[CookidooAdditionalItem]:
    """Edit additional items.

    Parameters
    ----------
    additional_items
        The additional items to change the the `name` value for

    Returns
    -------
    list[CookidooAdditionalItem]
        The list of the edited additional items

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    json_data = {
        "additionalItems": [
            {
                "id": additional_item.id,
                "name": additional_item.name,
            }
            for additional_item in additional_items
        ]
    }
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("pantry:edit-additional-items").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json(
            "post", url, "edit additional items", json=json_data
        ),
        "edit additional items",
    )
    return self._parse_result(
        "loading edited additional items",
        lambda: [
            cookidoo_additional_item_from_json(
                cast(AdditionalItemJSON, additional_item)
            )
            for additional_item in cast(Sequence[object], result["data"])
        ],
    )

edit_additional_items_ownership async

Edit ownership additional items.

Parameters:

Name Type Description Default
additional_items list[CookidooAdditionalItem]

The additional items to change the the is_owned value for

required

Returns:

Type Description
list[CookidooAdditionalItem]

The list of the edited additional items

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def edit_additional_items_ownership(
    self,
    additional_items: list[CookidooAdditionalItem],
) -> list[CookidooAdditionalItem]:
    """Edit ownership additional items.

    Parameters
    ----------
    additional_items
        The additional items to change the the `is_owned` value for

    Returns
    -------
    list[CookidooAdditionalItem]
        The list of the edited additional items

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    json_data = {
        "additionalItems": [
            {
                "id": additional_item.id,
                "isOwned": additional_item.is_owned,
                "ownedTimestamp": int(time.time()),
            }
            for additional_item in additional_items
        ]
    }
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path(
        "pantry:edit-additional-items-ownership"
    ).format(**self._cfg.localization.__dict__)
    result = self._ensure_mapping(
        await self._request_json(
            "post", url, "edit additional items ownership", json=json_data
        ),
        "edit additional items ownership",
    )
    return self._parse_result(
        "loading edited additional items",
        lambda: [
            cookidoo_additional_item_from_json(
                cast(AdditionalItemJSON, additional_item)
            )
            for additional_item in cast(Sequence[object], result["data"])
        ],
    )

remove_additional_items async

Remove additional items.

Parameters:

Name Type Description Default
additional_item_ids list[str]

The additional item ids to remove

required

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def remove_additional_items(
    self,
    additional_item_ids: list[str],
) -> None:
    """Remove additional items.

    Parameters
    ----------
    additional_item_ids
        The additional item ids to remove

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    json_data = {"additionalItemIDs": additional_item_ids}
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("pantry:remove-additional-items").format(
        **self._cfg.localization.__dict__
    )
    await self._request_json(
        "post",
        url,
        "remove additional items",
        json=json_data,
        parse_response=False,
    )

clear_shopping_list async

Remove all additional items, ingredients and recipes.

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def clear_shopping_list(
    self,
) -> None:
    """Remove all additional items, ingredients and recipes.

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("pantry:home").format(
        **self._cfg.localization.__dict__
    )
    await self._request_json(
        "delete", url, "clear shopping list", parse_response=False
    )

count_managed_collections async

Get managed collections.

Returns:

Type Description
tuple[int, int]

The number of managed collections and the number of pages

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def count_managed_collections(self) -> tuple[int, int]:
    """Get managed collections.

    Returns
    -------
    tuple[int, int]
        The number of managed collections and the number of pages

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """

    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("organize:api-managed-list").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json(
            "get",
            url,
            "loading managed collections",
            headers={"ACCEPT": MANAGED_COLLECTIONS_PATH_ACCEPT},
        ),
        "loading managed collections",
    )
    return self._parse_result(
        "loading managed collections",
        lambda: (
            cast(PaginationJSON, result["page"])["totalElements"],
            cast(PaginationJSON, result["page"])["totalPages"],
        ),
    )

get_managed_collections async

Get managed collections.

Parameters:

Name Type Description Default
page int

The page of the managed collections

0

Returns:

Type Description
list[CookidooCollection]

The list of the managed collections

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def get_managed_collections(self, page: int = 0) -> list[CookidooCollection]:
    """Get managed collections.

    Parameters
    ----------
    page
        The page of the managed collections

    Returns
    -------
    list[CookidooCollection]
        The list of the managed collections

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """

    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("organize:api-managed-list").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json(
            "get",
            url,
            "loading managed collections",
            params={"page": str(page)},
            headers={"ACCEPT": MANAGED_COLLECTIONS_PATH_ACCEPT},
        ),
        "loading managed collections",
    )
    return self._parse_result(
        "loading managed collections",
        lambda: [
            cookidoo_collection_from_json(cast(ManagedCollectionJSON, item))
            for item in cast(Sequence[object], result["managedlists"])
        ],
    )

add_managed_collection async

Add managed collections.

Parameters:

Name Type Description Default
managed_collection_id str

The managed collection id to add

required

Returns:

Type Description
CookidooCollection

The added managed collection

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def add_managed_collection(
    self,
    managed_collection_id: str,
) -> CookidooCollection:
    """Add managed collections.

    Parameters
    ----------
    managed_collection_id
        The managed collection id to add

    Returns
    -------
    CookidooCollection
        The added managed collection

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    json_data = {"collectionId": managed_collection_id}
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("organize:api-managed-list").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json(
            "post",
            url,
            "add managed collection",
            json=json_data,
            headers={"ACCEPT": MANAGED_COLLECTIONS_PATH_ACCEPT},
        ),
        "add managed collection",
    )
    return self._parse_result(
        "loading added managed collection",
        lambda: cookidoo_collection_from_json(
            cast(ManagedCollectionJSON, result["content"])
        ),
    )

remove_managed_collection async

Remove managed collection.

Parameters:

Name Type Description Default
managed_collection_id str

The managed collection id to remove

required

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def remove_managed_collection(
    self,
    managed_collection_id: str,
) -> None:
    """Remove managed collection.

    Parameters
    ----------
    managed_collection_id
        The managed collection id to remove

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("organize:api-managed-list-single").format(
        **self._cfg.localization.__dict__, id=managed_collection_id
    )
    await self._request_json(
        "delete",
        url,
        "remove managed collection",
        headers={"ACCEPT": MANAGED_COLLECTIONS_PATH_ACCEPT},
        parse_response=False,
    )

count_custom_collections async

Get custom collections.

Returns:

Type Description
tuple[int, int]

The number of custom collections and the number of pages

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def count_custom_collections(self) -> tuple[int, int]:
    """Get custom collections.

    Returns
    -------
    tuple[int, int]
        The number of custom collections and the number of pages

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """

    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("organize:api-custom-list").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json(
            "get",
            url,
            "loading custom collections",
            headers={"ACCEPT": CUSTOM_COLLECTIONS_PATH_ACCEPT},
        ),
        "loading custom collections",
    )
    return self._parse_result(
        "loading custom collections",
        lambda: (
            cast(PaginationJSON, result["page"])["totalElements"],
            cast(PaginationJSON, result["page"])["totalPages"],
        ),
    )

get_custom_collections async

Get custom collections.

Parameters:

Name Type Description Default
page int

The page of the custom collections

0

Returns:

Type Description
list[CookidooCollection]

The list of the custom collections

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def get_custom_collections(self, page: int = 0) -> list[CookidooCollection]:
    """Get custom collections.

    Parameters
    ----------
    page
        The page of the custom collections

    Returns
    -------
    list[CookidooCollection]
        The list of the custom collections

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """

    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("organize:api-custom-list").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json(
            "get",
            url,
            "loading custom collections",
            params={"page": str(page)},
            headers={"ACCEPT": CUSTOM_COLLECTIONS_PATH_ACCEPT},
        ),
        "loading custom collections",
    )
    return self._parse_result(
        "loading custom collections",
        lambda: [
            cookidoo_collection_from_json(cast(CustomCollectionJSON, item))
            for item in cast(Sequence[object], result["customlists"])
        ],
    )

add_custom_collection async

Add custom collections.

Parameters:

Name Type Description Default
custom_collection_name str

The custom collection name to add

required

Returns:

Type Description
CookidooCollection

The added custom collection

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def add_custom_collection(
    self,
    custom_collection_name: str,
) -> CookidooCollection:
    """Add custom collections.

    Parameters
    ----------
    custom_collection_name
        The custom collection name to add

    Returns
    -------
    CookidooCollection
        The added custom collection

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    json_data = {"title": custom_collection_name}
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("organize:api-custom-list").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json(
            "post",
            url,
            "add custom collection",
            json=json_data,
            headers={"ACCEPT": CUSTOM_COLLECTIONS_PATH_ACCEPT},
        ),
        "add custom collection",
    )
    return self._parse_result(
        "loading added custom collection",
        lambda: cookidoo_collection_from_json(
            cast(CustomCollectionJSON, result["content"])
        ),
    )

remove_custom_collection async

Remove custom collection.

Parameters:

Name Type Description Default
custom_collection_id str

The custom collection id to remove

required

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def remove_custom_collection(
    self,
    custom_collection_id: str,
) -> None:
    """Remove custom collection.

    Parameters
    ----------
    custom_collection_id
        The custom collection id to remove

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("organize:api-custom-list-modify").format(
        **self._cfg.localization.__dict__, id=custom_collection_id
    )
    await self._request_json(
        "delete",
        url,
        "remove custom collection",
        headers={"ACCEPT": CUSTOM_COLLECTIONS_PATH_ACCEPT},
        parse_response=False,
    )

add_recipes_to_custom_collection async

Add recipes to a custom collections.

Parameters:

Name Type Description Default
custom_collection_id str

The custom collection to add the recipes to

required
recipe_ids list[str]

The recipe ids to add to a custom collection

required

Returns:

Type Description
CookidooCollection

The changed custom collection

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def add_recipes_to_custom_collection(
    self,
    custom_collection_id: str,
    recipe_ids: list[str],
) -> CookidooCollection:
    """Add recipes to a custom collections.

    Parameters
    ----------
    custom_collection_id
        The custom collection to add the recipes to
    recipe_ids
        The recipe ids to add to a custom collection

    Returns
    -------
    CookidooCollection
        The changed custom collection

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    json_data = {"recipeIds": recipe_ids}
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("organize:api-custom-list-modify").format(
        **self._cfg.localization.__dict__, id=custom_collection_id
    )
    result = self._ensure_mapping(
        await self._request_json(
            "put", url, "add recipes to custom collection", json=json_data
        ),
        "add recipes to custom collection",
    )
    return self._parse_result(
        "loading added recipes",
        lambda: cookidoo_collection_from_json(
            cast(CustomCollectionJSON, result["content"])
        ),
    )

remove_recipe_from_custom_collection async

Remove recipe from a custom collections.

Parameters:

Name Type Description Default
custom_collection_id str

The custom collection to remove the recipe from

required
recipe_id str

The recipe id to remove from a custom collection

required

Returns:

Type Description
CookidooCollection

The changed custom collection

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def remove_recipe_from_custom_collection(
    self,
    custom_collection_id: str,
    recipe_id: str,
) -> CookidooCollection:
    """Remove recipe from a custom collections.

    Parameters
    ----------
    custom_collection_id
        The custom collection to remove the recipe from
    recipe_id
        The recipe id to remove from a custom collection

    Returns
    -------
    CookidooCollection
        The changed custom collection

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("organize:api-custom-list-recipe").format(
        **self._cfg.localization.__dict__,
        id=custom_collection_id,
        recipe=recipe_id,
    )
    result = self._ensure_mapping(
        await self._request_json(
            "delete", url, "remove recipe from custom collection"
        ),
        "remove recipe from custom collection",
    )
    return self._parse_result(
        "loading removed recipe",
        lambda: cookidoo_collection_from_json(
            cast(CustomCollectionJSON, result["content"])
        ),
    )

get_recipes_in_calendar_week async

Get recipes in a calendar week.

The planning endpoint returns catalogue recipes in full but custom (user-created) recipes only as customerRecipeIds. By default those IDs are exposed as-is on :attr:CookidooCalendarDay.customer_recipe_ids and are not part of :attr:CookidooCalendarDay.recipes.

Parameters:

Name Type Description Default
day date

The date specifying the calendar week

required
resolve_custom_recipes bool

When True, load every custom recipe referenced in the week (one request per unique ID, issued concurrently) and append it to the matching day's recipes with is_custom=True, so the list reflects everything that is planned. IDs that cannot be loaded (e.g. a custom recipe deleted after it was planned) are skipped and stay available in customer_recipe_ids only.

False

Returns:

Type Description
list[CookidooCalendarDay]

The list of the calendar days with recipes

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def get_recipes_in_calendar_week(
    self, day: date, *, resolve_custom_recipes: bool = False
) -> list[CookidooCalendarDay]:
    """Get recipes in a calendar week.

    The planning endpoint returns catalogue recipes in full but custom
    (user-created) recipes only as ``customerRecipeIds``. By default those
    IDs are exposed as-is on :attr:`CookidooCalendarDay.customer_recipe_ids`
    and are *not* part of :attr:`CookidooCalendarDay.recipes`.

    Parameters
    ----------
    day
        The date specifying the calendar week
    resolve_custom_recipes
        When ``True``, load every custom recipe referenced in the week
        (one request per unique ID, issued concurrently) and append it to
        the matching day's ``recipes`` with ``is_custom=True``, so the
        list reflects everything that is planned. IDs that cannot be
        loaded (e.g. a custom recipe deleted after it was planned) are
        skipped and stay available in ``customer_recipe_ids`` only.

    Returns
    -------
    list[CookidooCalendarDay]
        The list of the calendar days with recipes

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """

    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("planning:api-my-week-from-date").format(
        **self._cfg.localization.__dict__, day=day.isoformat()
    )
    result = self._ensure_mapping(
        await self._request_json("get", url, "loading recipes in calendar week"),
        "loading recipes in calendar week",
    )
    calendar_days = self._parse_result(
        "loading recipes in calendar week",
        lambda: [
            cookidoo_calendar_day_from_json(
                cast(CalendarDayJSON, calendar_day), self._cfg.localization
            )
            for calendar_day in cast(Sequence[object], result["myDays"])
        ],
    )
    if resolve_custom_recipes:
        await self._resolve_calendar_custom_recipes(calendar_days)
    return calendar_days

add_recipes_to_calendar async

Add recipes to a calendar.

Parameters:

Name Type Description Default
day date

The date to add the recipes to in the calendar

required
recipe_ids list[str]

The recipe ids to add to the calendar

required

Returns:

Type Description
CookidooCalendarDay

The changed calendar day

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def add_recipes_to_calendar(
    self,
    day: date,
    recipe_ids: list[str],
) -> CookidooCalendarDay:
    """Add recipes to a calendar.

    Parameters
    ----------
    day
        The date to add the recipes to in the calendar
    recipe_ids
        The recipe ids to add to the calendar

    Returns
    -------
    CookidooCalendarDay
        The changed calendar day

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    json_data = {"recipeIds": recipe_ids, "dayKey": day.isoformat()}
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("planning:api-my-day").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json(
            "put", url, "add recipes to calendar", json=json_data
        ),
        "add recipes to calendar",
    )
    return self._parse_result(
        "loading added recipes",
        lambda: cookidoo_calendar_day_from_json(
            cast(CalendarDayJSON, result["content"]),
            self._cfg.localization,
        ),
    )

remove_recipe_from_calendar async

Remove recipe from calendar.

Parameters:

Name Type Description Default
day date

The date to remove the recipe from in the calendar

required
recipe_id str

The recipe id to remove from the calendar

required

Returns:

Type Description
CookidooCalendarDay

The changed calendar day

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def remove_recipe_from_calendar(
    self,
    day: date,
    recipe_id: str,
) -> CookidooCalendarDay:
    """Remove recipe from calendar.

    Parameters
    ----------
    day
        The date to remove the recipe from in the calendar
    recipe_id
        The recipe id to remove from the calendar

    Returns
    -------
    CookidooCalendarDay
        The changed calendar day

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("planning:api-my-day-recipes").format(
        **self._cfg.localization.__dict__,
        day=day.isoformat(),
        recipe=recipe_id,
    )
    result = self._ensure_mapping(
        await self._request_json("delete", url, "remove recipe from calendar"),
        "remove recipe from calendar",
    )
    if result.get("content") is None:
        # The API returns a null content when the removed recipe was the
        # last one for the day, since the (now empty) day no longer
        # exists as an entity.
        return self._empty_calendar_day(day)
    return self._parse_result(
        "loading removed recipe",
        lambda: cookidoo_calendar_day_from_json(
            cast(CalendarDayJSON, result["content"]),
            self._cfg.localization,
        ),
    )

add_custom_recipes_to_calendar async

Add custom recipes to a calendar.

Parameters:

Name Type Description Default
day date

The date to add the custom recipes to in the calendar

required
recipe_ids list[str]

The recipe ids to add to the calendar

required

Returns:

Type Description
CookidooCalendarDay

The changed calendar day

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def add_custom_recipes_to_calendar(
    self,
    day: date,
    recipe_ids: list[str],
) -> CookidooCalendarDay:
    """Add custom recipes to a calendar.

    Parameters
    ----------
    day
        The date to add the custom recipes to in the calendar
    recipe_ids
        The recipe ids to add to the calendar

    Returns
    -------
    CookidooCalendarDay
        The changed calendar day

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    json_data = {
        "recipeIds": recipe_ids,
        "dayKey": day.isoformat(),
        "recipeSource": "CUSTOMER",
    }
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("planning:api-my-day").format(
        **self._cfg.localization.__dict__
    )
    result = self._ensure_mapping(
        await self._request_json(
            "put", url, "add custom recipes to calendar", json=json_data
        ),
        "add custom recipes to calendar",
    )
    return self._parse_result(
        "loading added custom recipes",
        lambda: cookidoo_calendar_day_from_json(
            cast(CalendarDayJSON, result["content"]),
            self._cfg.localization,
        ),
    )

remove_custom_recipe_from_calendar async

Remove custom recipe from calendar.

Parameters:

Name Type Description Default
day date

The date to remove the custom recipe from in the calendar

required
recipe_id str

The custom recipe id to remove from the calendar

required

Returns:

Type Description
CookidooCalendarDay

The changed calendar day

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If the request fails.

CookidooParseException

If the parsing of the request response fails.

Source code in cookidoo_api/cookidoo.py
async def remove_custom_recipe_from_calendar(
    self,
    day: date,
    recipe_id: str,
) -> CookidooCalendarDay:
    """Remove custom recipe from calendar.

    Parameters
    ----------
    day
        The date to remove the custom recipe from in the calendar
    recipe_id
        The custom recipe id to remove from the calendar

    Returns
    -------
    CookidooCalendarDay
        The changed calendar day

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If the request fails.
    CookidooParseException
        If the parsing of the request response fails.

    """
    await self._ensure_endpoints()
    url = self.api_endpoint / self._path("planning:api-my-day-recipes").format(
        **self._cfg.localization.__dict__,
        day=day.isoformat(),
        recipe=recipe_id,
    )
    result = self._ensure_mapping(
        await self._request_json(
            "delete",
            url,
            "remove custom recipe from calendar",
            params={"recipeSource": "CUSTOMER"},
        ),
        "remove custom recipe from calendar",
    )
    if result.get("content") is None:
        # The API returns a null content when the removed recipe was the
        # last one for the day, since the (now empty) day no longer
        # exists as an entity.
        return self._empty_calendar_day(day)
    return self._parse_result(
        "loading custom removed recipe",
        lambda: cookidoo_calendar_day_from_json(
            cast(CalendarDayJSON, result["content"]),
            self._cfg.localization,
        ),
    )

CookidooAuthException

Bases: CookidooException

When an authentication error is encountered.

CookidooConfigException

Bases: CookidooException

When the config is invalid.

CookidooException

Bases: Exception

General exception occurred.

CookidooParseException

Bases: CookidooException

When data could not be parsed.

CookidooRequestException

Bases: CookidooException

When a request returns an error.

CookidooResponseException

Bases: CookidooException

When a response could not be parsed.

CookidooUnavailableException

Bases: CookidooException

When the network or server is not available.

CookidooRemoteMonitoring

Receives live cook state for the appliances of an account.

The FCM credentials are worth persisting: reusing them keeps the same push token across restarts instead of leaving a new one registered every time. Pass them back in via credentials and keep them fresh with on_credentials.

Init the remote monitoring receiver.

Parameters:

Name Type Description Default
cookidoo Cookidoo

A logged-in client, used to register and unregister the push token.

required
on_cooking_activity Callable[[CookidooCookingActivity], None]

Called with every decoded cook-state update. Invoked from the event loop the receiver runs on.

required
mobile_app_id str

A stable per-installation identifier for this client.

required
session ClientSession | None

Client session for the Firebase requests. A private one is created when omitted.

None
credentials dict[str, Any] | None

FCM credentials from a previous run, to reuse its push token.

None
on_credentials Callable[[dict[str, Any]], None] | None

Called whenever the FCM credentials change, for persistence.

None
Source code in cookidoo_api/remote_monitoring.py
def __init__(
    self,
    cookidoo: Cookidoo,
    on_cooking_activity: Callable[[CookidooCookingActivity], None],
    *,
    mobile_app_id: str,
    session: ClientSession | None = None,
    credentials: dict[str, Any] | None = None,
    on_credentials: Callable[[dict[str, Any]], None] | None = None,
) -> None:
    """Init the remote monitoring receiver.

    Parameters
    ----------
    cookidoo
        A logged-in client, used to register and unregister the push token.
    on_cooking_activity
        Called with every decoded cook-state update. Invoked from the event
        loop the receiver runs on.
    mobile_app_id
        A stable per-installation identifier for this client.
    session
        Client session for the Firebase requests. A private one is created
        when omitted.
    credentials
        FCM credentials from a previous run, to reuse its push token.
    on_credentials
        Called whenever the FCM credentials change, for persistence.

    """
    self._cookidoo = cookidoo
    self._on_cooking_activity = on_cooking_activity
    self._mobile_app_id = mobile_app_id
    self._session = session
    self._credentials = credentials
    self._on_credentials = on_credentials
    self._client: FcmPushClient | None = None
    self._token: str | None = None
    self._registered_token: str | None = None
    self._reregister_task: asyncio.Task[None] | None = None

credentials property

The current FCM credentials, for persistence.

token property

The current FCM registration token. None until started.

is_connected property

Whether the receiver is logged in and able to receive pushes.

start async

Start listening and register for cook-state pushes.

Raises:

Type Description
CookidooAuthException

When the access token is not valid anymore

CookidooRequestException

If registering the push token fails.

CookidooParseException

If the remote-monitoring endpoints cannot be resolved.

Source code in cookidoo_api/remote_monitoring.py
async def start(self) -> None:
    """Start listening and register for cook-state pushes.

    Raises
    ------
    CookidooAuthException
        When the access token is not valid anymore
    CookidooRequestException
        If registering the push token fails.
    CookidooParseException
        If the remote-monitoring endpoints cannot be resolved.

    """
    client = FcmPushClient(
        self._handle_message,
        FcmRegisterConfig(
            project_id=FCM_PROJECT_ID,
            app_id=FCM_APP_ID,
            api_key=FCM_API_KEY,
            messaging_sender_id=FCM_SENDER_ID,
        ),
        self._credentials,
        self._handle_credentials,
        http_client_session=self._session,
    )
    self._token = await self._checkin(client)
    await client.start()
    # Only now is there something to shut down: a client that failed to
    # check in cannot be stopped, and stop() must not mask that failure.
    self._client = client
    await self._register(self._token)

stop async

Stop listening and drop the push token registration.

Source code in cookidoo_api/remote_monitoring.py
async def stop(self) -> None:
    """Stop listening and drop the push token registration."""
    if self._reregister_task is not None:
        self._reregister_task.cancel()
        self._reregister_task = None
    if self._registered_token is not None:
        try:
            await self._cookidoo.unregister_push_token(self._registered_token)
        except Exception:
            _LOGGER.debug("Unregistering the push token failed", exc_info=True)
        self._registered_token = None
    if self._client is not None:
        await self._client.stop()
        self._client = None

CookidooAdditionalItem dataclass

Bases: CookidooItem

Cookidoo additional item type.

CookidooAuthData dataclass

OAuth2 tokens obtained from a login, for persistence and restore.

expires_at is a POSIX timestamp (seconds) for the access token.

CookidooCategory dataclass

Cookidoo category type.

Attributes:

Name Type Description
id str

The id of the category

name str

The label of the category

notes str

The additional information of the category

CookidooChapter dataclass

Cookidoo chapter type.

Attributes:

Name Type Description
title

The title of the chapter

recipes list[CookidooChapterRecipe]

The recipes in the chapter

CookidooChapterRecipe dataclass

Cookidoo chapter recipe type.

Attributes:

Name Type Description
id str

The id of the recipe

name str

The label of the recipe

total_time int

The time for the recipe

CookidooCollection dataclass

Cookidoo collection type.

Attributes:

Name Type Description
id str

The id of the collection

title

The title of the collection

description str | None

The description of the collection

chapters list[CookidooChapter]

The recipes in the collection

CookidooConfig dataclass

Cookidoo config type.

Attributes:

Name Type Description
localization CookidooLocalizationConfig

The localization for the api including country, language and url

email str

The email to login

password str

The password to login

client_id str

The OAuth2 client id to run the login flow as

redirect_uri str

The OAuth2 redirect uri registered for client_id

The login runs as a public client (authorization code + PKCE, no client
secret), so both values are public identifiers rather than credentials and
they default to the ones of the Cookidoo mobile app. Callers do not need to
set them; see ``docs/oauth-client.md``.

CookidooCookingActivity dataclass

Live cook state pushed by an appliance's remote monitoring.

Values the recipe does not provide are None (the app renders "---" for an unset current temperature, which is normalised to None here).

is_active property

Whether a cook is currently running or paused.

CookidooCookState

Bases: StrEnum

State of an ongoing remote-monitored cook.

CookidooCreateCustomRecipe dataclass

Input model for creating a custom recipe.

CookidooCustomAnnotation dataclass

Extensible annotation for API types not modeled by this library.

CookidooCustomRecipe dataclass

Cookidoo custom recipe type.

Attributes:

Name Type Description
id str

The id of the recipe

name str

The label of the recipe

ingredients list[str]

The ingredients of the recipe

instructions list[str | CookidooInstruction]

The instructions of the recipe

tools list[str]

The tools needed for the recipe

serving_size int

The service size of the recipe

active_time int

The time needed preparing the recipe [in seconds]

total_time int

The time needed until the recipe is ready [in seconds]

thumbnail str | None

The thumbnail image URL (small preview)

image str | None

The full-size image URL

url str

The URL of the recipe

CookidooDevice dataclass

A paired Thermomix appliance on the account.

CookidooIngredient dataclass

Cookidoo ingredient type.

Attributes:

Name Type Description
id str

The id of the ingredient

name str

The label of the ingredient

description str

The description of the item, including the quantity or other helpful information

preparation str | None

How the ingredient is to be prepared ("peeled", ", in pieces"), as sent by the API. None when there is none — the API sends an empty string.

CookidooIngredientAnnotation dataclass

Reference an ingredient occurrence within an instruction.

CookidooIngredientItem dataclass

Bases: CookidooItem

Cookidoo ingredient item type.

Attributes:

Name Type Description
description str

The description of the item, including the quantity or other helpful information

preparation str | None

How the ingredient is to be prepared (", in pieces"), as sent by the API. None when there is none — the API sends an empty string.

CookidooInstruction dataclass

Recipe instruction with optional guided-cooking settings.

CookidooItem dataclass

Cookidoo item type.

Attributes:

Name Type Description
id str

The id of the item

name str

The label of the item

CookidooLocalizationConfig dataclass

A localization config class.

CookidooModeAnnotation dataclass

Thermomix mode settings anchored to an instruction slot.

CookidooRecipeCollection dataclass

Cookidoo recipe collection type.

Attributes:

Name Type Description
id str

The id of the collection

name str

The label of the collection

additional_information

The additional information of the collection

CookidooSearchRecipeHit dataclass

A single recipe hit from Cookidoo search.

Attributes:

Name Type Description
id str

The id of the recipe

name str

The title of the recipe

thumbnail str | None

The thumbnail image URL (small preview)

image str | None

The full-size image URL

url str

The URL of the recipe

CookidooSearchResult dataclass

Cookidoo search result type.

Attributes:

Name Type Description
recipes list[CookidooSearchRecipeHit]

List of recipe hits matching the search

total int

Total number of matching recipes

CookidooShoppingRecipe dataclass

Cookidoo shopping recipe type.

Attributes:

Name Type Description
id str

The id of the recipe

name str

The label of the recipe

ingredients list[CookidooIngredient]

The ingredients of the recipe

thumbnail str | None

The thumbnail image URL (small preview)

image str | None

The full-size image URL

url str

The URL of the recipe

CookidooShoppingRecipeDetails dataclass

Bases: CookidooShoppingRecipe

Cookidoo recipe details type.

Attributes:

Name Type Description
difficulty str

The difficulty of the recipe

notes list[str]

Hints and additional information about the recipe

categories list[CookidooCategory]

The categories of the recipe

collections list[CookidooRecipeCollection]

The collections of the recipe

utensils list[str]

The utensils needed for the recipe

serving_size int

The service size of the recipe

active_time int

The time needed preparing the recipe [in seconds]

total_time int

The time needed until the recipe is ready [in seconds]

nutrition_groups list[CookidooNutritionGroup]

The nutrition groups of the recipe (from API, may be empty)

step_groups list[CookidooRecipeStepGroup]

The grouped cooking instructions for the recipe (from API, may be empty). Instruction text is returned as HTML markup, as sent by the API.

CookidooStepSettings dataclass

Structured guided-cooking settings for an instruction.

CookidooSubscription dataclass

A subscription class.

CookidooTemperatureSetting dataclass

Temperature value used by an instruction annotation.

CookidooTTSAnnotation dataclass

Text-to-speech cooking settings anchored to an instruction slot.

CookidooUpdateCustomRecipe dataclass

Partial update model for an existing custom recipe.

image=None reuses the existing customer-recipe photo when its path can be recovered. A supplied image must be a customer-recipe path or filename; Cookidoo may reject an image that belongs to a different recipe.

CookidooUserInfo dataclass

A user info class.

ThermomixBrowningPower

Bases: StrEnum

Browning mode power levels.

ThermomixDirection

Bases: StrEnum

Thermomix rotation directions.

ThermomixMachineType

Bases: StrEnum

Thermomix machine types.

ThermomixMode

Bases: StrEnum

Thermomix guided cooking modes.

ThermomixSpeed

Bases: StrEnum

Recommended Thermomix speeds.

ThermomixSteamingAccessory

Bases: StrEnum

Accessories for steaming mode.

ThermomixTemperature

Bases: StrEnum

Recommended Thermomix temperatures (Celsius).

cooking_activity_from_push

Convert a remote-monitoring push payload into a cooking activity.

Appliance state is delivered out of band (a Firebase Cloud Messaging data message) rather than as an HTTP response, so consumers receive it via their own push channel and decode it here. Both the on-the-wire field names (leadingText/trailingText/completedDate/staleDate/…) and the app's parsed names are accepted.

Source code in cookidoo_api/helpers.py
def cooking_activity_from_push(
    data: Mapping[str, object],
) -> CookidooCookingActivity:
    """Convert a remote-monitoring push payload into a cooking activity.

    Appliance state is delivered out of band (a Firebase Cloud Messaging data
    message) rather than as an HTTP response, so consumers receive it via their
    own push channel and decode it here. Both the on-the-wire field names
    (``leadingText``/``trailingText``/``completedDate``/``staleDate``/…) and the
    app's parsed names are accepted.
    """

    def first(*keys: str) -> object:
        for key in keys:
            if key in data and data[key] is not None:
                return data[key]
        return None

    remaining_raw = first("remainingDuration")
    remaining: int | None = None
    if isinstance(remaining_raw, (int, str)):
        try:
            remaining = int(remaining_raw)
        except ValueError:
            remaining = None
    completed_at = _push_timestamp(first("completedDate", "completedTimestamp"))
    end_at = _push_timestamp(first("endTimestamp"))
    # The wire payload has no remainingDuration; derive it from the finish time.
    if remaining is None:
        finish = completed_at or end_at
        if finish is not None:
            remaining = max(0, int((finish - datetime.now(UTC)).total_seconds()))

    state_raw = first("state")
    state = CookidooCookState(str(state_raw).upper()) if state_raw is not None else None
    recipe_type = first("recipeType")

    return CookidooCookingActivity(
        device_id=str(first("deviceId") or ""),
        cooking_activity_id=cast("str | None", first("cookingActivityId")),
        state=state,
        recipe_id=cast("str | None", first("recipeId")),
        recipe_type=str(recipe_type).upper() if recipe_type is not None else None,
        recipe_name=cast(
            "str | None", first("leadingText", "leadingInfoText", "infoText")
        ),
        step=cast("str | None", first("trailingText", "trailingInfoText")),
        remaining_seconds=remaining,
        is_time_estimated=_push_bool(data.get("isTimeEstimated", False)),
        current_temperature=_push_number(first("primaryInfo")),
        target_temperature=_push_number(first("secondaryInfo")),
        message_title=cast("str | None", first("messageTitle")),
        message_body=cast("str | None", first("messageBody")),
        message_criticality=cast("str | None", first("messageCriticality")),
        completed_at=completed_at,
        stale_at=_push_timestamp(first("staleDate", "staleTimestamp")),
    )

get_country_options async

Get a list of possible country options.

Source code in cookidoo_api/helpers.py
async def get_country_options() -> list[str]:
    """Get a list of possible country options."""
    return list({option.country_code for option in await get_localization_options()})

get_language_options async

Get a list of possible language options.

Source code in cookidoo_api/helpers.py
async def get_language_options() -> list[str]:
    """Get a list of possible language options."""
    return list({option.language for option in await get_localization_options()})

get_localization_options async

Get a list of possible localization options.

Source code in cookidoo_api/helpers.py
async def get_localization_options(
    country: str | None = None,
    language: str | None = None,
) -> list[CookidooLocalizationConfig]:
    """Get a list of possible localization options."""
    return await __get_localization_options(country, language)