Ejemplo n.º 1
0
    async def get_token(self) -> str | None:
        """Get or refresh the authentication token."""
        authentication_data: dict[str, str | None] = dict(
            email_address=self.email, password=self.password, device_id=self._device_id
        )

        token: str | None = None

        session = self._session if self._session else aiohttp.ClientSession()

        try:
            raw_response: aiohttp.ClientResponse = await session.post(
                url=AUTH_RESOURCE, data=authentication_data, headers=self._generate_headers()
            )

            if raw_response.status == HTTPStatus.OK:

                response: dict[str, Any] = await raw_response.json()

                if "data" in response and "token" in response["data"]:
                    token = self._auth_token = response["data"]["token"]

            elif raw_response.status == HTTPStatus.NOT_MODIFIED:
                # Etag header matched, no new data available
                pass

            elif raw_response.status == HTTPStatus.UNAUTHORIZED:
                self._auth_token = None
                raise SurePetcareAuthenticationError()

            else:
                logger.debug("Response from %s: %s", AUTH_RESOURCE, raw_response)
                raise SurePetcareAPIError(
                    resource=AUTH_RESOURCE,
                    response=raw_response,
                    message="unknown response from sure petcare api",
                )

            return token

        except asyncio.TimeoutError as error:
            logger.debug("Timeout while calling %s: %s", AUTH_RESOURCE, error)
            raise SurePetcareConnectionError()
        except (aiohttp.ClientError, AttributeError) as error:
            logger.debug("Failed to fetch %s: %s", AUTH_RESOURCE, error)
            raise SurePetcareError()
        finally:
            if not self._session:
                await session.close()
Ejemplo n.º 2
0
    def get_token(self) -> Optional[str]:
        """Get or refresh the authentication token."""
        authentication_data: Dict[str, Optional[str]] = dict(
            email_address=self.email, password=self.password, device_id=self._device_id
        )

        token: Optional[str] = None

        try:
            raw_response: requests.Response = requests.post(
                url=AUTH_RESOURCE, data=authentication_data, headers=self._generate_headers()
            )

            if raw_response.status_code == HTTPStatus.OK:

                response: Dict[str, Any] = raw_response.json()

                if "data" in response and "token" in response["data"]:
                    token = self._auth_token = response["data"]["token"]

            elif raw_response.status_code == HTTPStatus.NOT_MODIFIED:
                # Etag header matched, no new data available
                pass

            elif raw_response.status_code == HTTPStatus.UNAUTHORIZED:
                self._auth_token = None
                raise SurePetcareAuthenticationError()

            else:
                logger.debug("Response from %s: %s", AUTH_RESOURCE, raw_response)
                raise SurePetcareError()

            return token

        except asyncio.TimeoutError as error:
            logger.debug("Timeout while calling %s: %s", AUTH_RESOURCE, error)
            raise SurePetcareConnectionError()
        except (aiohttp.ClientError, AttributeError) as error:
            logger.debug("Failed to fetch %s: %s", AUTH_RESOURCE, error)
            raise SurePetcareError()
Ejemplo n.º 3
0
    async def call(
        self,
        method: str,
        resource: str,
        data: dict[str, Any] | None = None,
        second_try: bool = False,
        **_: Any,
    ) -> dict[str, Any] | None:
        """Retrieve the flap data/state."""

        logger.debug("self._auth_token: %s", self._auth_token)
        if not self._auth_token:
            self._auth_token = await self.get_token()

        if method not in ["GET", "PUT", "POST"]:
            raise HTTPException("unknown http method: %d", str(method))

        response_data = None

        session = self._session if self._session else aiohttp.ClientSession()

        try:
            with async_timeout.timeout(self._api_timeout):
                headers = self._generate_headers()

                # use etag if available
                if resource in self._etags:
                    headers[ETAG] = str(self._etags.get(resource))
                    logger.debug("using available etag '%s' in headers: %s",
                                 ETAG, headers)

                await session.options(resource, headers=headers)
                response: aiohttp.ClientResponse = await session.request(
                    method, resource, headers=headers, data=data)

                if response.status == HTTPStatus.OK or response.status == HTTPStatus.CREATED:

                    self.resources[
                        resource] = response_data = await response.json()

                    if ETAG in response.headers:
                        self._etags[resource] = response.headers[ETAG].strip(
                            '"')

                elif response.status == HTTPStatus.NOT_MODIFIED:
                    # Etag header matched, no new data available
                    pass

                elif response.status == HTTPStatus.UNAUTHORIZED:
                    logger.debug("AuthenticationError! Try: %s: %s",
                                 second_try, response)
                    self._auth_token = None
                    if not second_try:
                        token_refreshed = self.get_token()
                        if token_refreshed:
                            await self.call(method="GET",
                                            resource=resource,
                                            second_try=True)

                    raise SurePetcareAuthenticationError()

                else:
                    logger.info(f"Response from {resource}:\n{response}")

                return response_data

        except (asyncio.TimeoutError, aiohttp.ClientError):
            logger.error("Can not load data from %s", resource)
            raise SurePetcareConnectionError()
        finally:
            if not self._session:
                await session.close()
Ejemplo n.º 4
0
        self._device_id: str = str(uuid1())

        # connection settings
        self._api_timeout: int = api_timeout

        self._surepy_version: str = surepy_version

        # api token management
        self._auth_token: str | None = None
        if auth_token and token_seems_valid(auth_token):
            self._auth_token = auth_token
        elif token := find_token():
            self._auth_token = token
        else:
            # no valid credentials/token
            SurePetcareAuthenticationError(
                "sorry 🐾 no valid credentials/token found ¯\\_(ツ)_/¯")

        # storage for received api data
        self.resources: dict[str, Any] = {}
        # storage for etags
        self._etags: dict[str, str] = {}

        logger.debug("initialization completed | vars(): %s", vars())

    def _generate_headers(self) -> dict[str, str]:
        """Build a HTTP header accepted by the API"""
        user_agent = (SUREPY_USER_AGENT.format(
            version=self._surepy_version) if self._surepy_version else None)

        return {
            HOST: "app.api.surehub.io",