Module push_to_3yourmind.api.base

Base classes

Expand source code
"""
Base classes
"""
import os
import typing as t

import requests

from push_to_3yourmind import exceptions
from push_to_3yourmind import types
from push_to_3yourmind.logger import logger


__all__ = ["BaseAPI"]


class BaseAPI:
    """
    Base class for all namespaced API methods. Not to be instantiated directly.
    """

    def __init__(self, access_token: str, base_url: str):
        """
        Args:
            access_token: to create a token, open `/admin/auth/user/`, and click
                "Create token" in the user list.
            base_url: application URL, ex. https://app.3yourmind.com
        """
        self._api_prefix = "api/v2.0"
        self._access_token = access_token
        self._base_url = base_url

    def _get_url(self, sub_path: str) -> str:
        """
        Formats API endpoint absolute URL

        :param sub_path:
        :return:
        """

        path = os.path.join(self._api_prefix, sub_path).lstrip("/")
        return os.path.join(self._base_url, path)

    def _get_headers(self):
        return {"Authorization": f"Token {self._access_token}"}

    def _request(
        self,
        method: types.RequestMethod,
        sub_path: str,
        **kwargs: t.Any,
    ) -> types.AnyResponse:
        """
        Main wrapper for request to the API. Together with required positional arguments
        accepts keyword arguments that are passed to `requests.request` function.

        - json: used to send JSON data with POST, PUT or PATCH request
        - files: send files using multipart/form-urlencoded content type
        - params: send GET query params ({"sort": "date"} will result in ?sort=date)

        :param method: HTTP method name: GET, PUT, POST etc
        :param sub_path: relative to /api/v2.0/ or absolute subpath to the API endpoint.
            Relative subpath should not contain leading backslash: "user-panel/baskets/":
            "http://api.domain.com/api/v2.0/user-panel/baskets/"

            Absolute path must have a leading backslash resulting in URL: "/profile/"
            "http://api.domain.com/profile/"
        :param kwargs:
        :return: JSON response from API if API response code is 200..299. Otherwise,
            raises an exception (a subclass of `push_to_3yourmind.exceptions.BasePushTo3YourmindAPIException`)
        """

        url = self._get_url(sub_path)
        logger.debug(f"Request {method} to {url}")
        response = requests.request(
            method=method,
            url=url,
            headers=self._get_headers(),
            **kwargs,
        )

        if 200 <= response.status_code < 500:
            if response.content:
                response_payload = response.json()
            else:
                response_payload = ""

            if response.status_code == 400:
                raise exceptions.BadRequest(response_payload)
            elif response.status_code == 401:
                raise exceptions.Unauthorized(response_payload)
            elif response.status_code == 403:
                raise exceptions.AccessDenied(response_payload)
            elif response.status_code == 404:
                raise exceptions.ObjectNotFound(response_payload)
            elif response.status_code == 405:
                raise exceptions.MethodNotAllowed(response_payload)
            return response_payload
        else:
            raise exceptions.ServerError(response.content)

    @staticmethod
    def _get_parameters(**kwargs) -> t.Dict[str, t.Any]:
        return {
            key: value for key, value in kwargs.items() if value is not types.NoValue
        }

Classes

class BaseAPI (access_token: str, base_url: str)

Base class for all namespaced API methods. Not to be instantiated directly.

Args

access_token
to create a token, open /admin/auth/user/, and click "Create token" in the user list.
base_url
application URL, ex. https://app.3yourmind.com
Expand source code
class BaseAPI:
    """
    Base class for all namespaced API methods. Not to be instantiated directly.
    """

    def __init__(self, access_token: str, base_url: str):
        """
        Args:
            access_token: to create a token, open `/admin/auth/user/`, and click
                "Create token" in the user list.
            base_url: application URL, ex. https://app.3yourmind.com
        """
        self._api_prefix = "api/v2.0"
        self._access_token = access_token
        self._base_url = base_url

    def _get_url(self, sub_path: str) -> str:
        """
        Formats API endpoint absolute URL

        :param sub_path:
        :return:
        """

        path = os.path.join(self._api_prefix, sub_path).lstrip("/")
        return os.path.join(self._base_url, path)

    def _get_headers(self):
        return {"Authorization": f"Token {self._access_token}"}

    def _request(
        self,
        method: types.RequestMethod,
        sub_path: str,
        **kwargs: t.Any,
    ) -> types.AnyResponse:
        """
        Main wrapper for request to the API. Together with required positional arguments
        accepts keyword arguments that are passed to `requests.request` function.

        - json: used to send JSON data with POST, PUT or PATCH request
        - files: send files using multipart/form-urlencoded content type
        - params: send GET query params ({"sort": "date"} will result in ?sort=date)

        :param method: HTTP method name: GET, PUT, POST etc
        :param sub_path: relative to /api/v2.0/ or absolute subpath to the API endpoint.
            Relative subpath should not contain leading backslash: "user-panel/baskets/":
            "http://api.domain.com/api/v2.0/user-panel/baskets/"

            Absolute path must have a leading backslash resulting in URL: "/profile/"
            "http://api.domain.com/profile/"
        :param kwargs:
        :return: JSON response from API if API response code is 200..299. Otherwise,
            raises an exception (a subclass of `push_to_3yourmind.exceptions.BasePushTo3YourmindAPIException`)
        """

        url = self._get_url(sub_path)
        logger.debug(f"Request {method} to {url}")
        response = requests.request(
            method=method,
            url=url,
            headers=self._get_headers(),
            **kwargs,
        )

        if 200 <= response.status_code < 500:
            if response.content:
                response_payload = response.json()
            else:
                response_payload = ""

            if response.status_code == 400:
                raise exceptions.BadRequest(response_payload)
            elif response.status_code == 401:
                raise exceptions.Unauthorized(response_payload)
            elif response.status_code == 403:
                raise exceptions.AccessDenied(response_payload)
            elif response.status_code == 404:
                raise exceptions.ObjectNotFound(response_payload)
            elif response.status_code == 405:
                raise exceptions.MethodNotAllowed(response_payload)
            return response_payload
        else:
            raise exceptions.ServerError(response.content)

    @staticmethod
    def _get_parameters(**kwargs) -> t.Dict[str, t.Any]:
        return {
            key: value for key, value in kwargs.items() if value is not types.NoValue
        }

Subclasses