mpesa.portalsdk.api — tekmpesa 1.0.0 documentation

# Source code for mpesa.portalsdk.api

    """Mpesa PortalSDK API Helper Module."""
    import json
    from enum import Enum

    import requests
    from base64 import b64decode, b64encode
    from Crypto.PublicKey import RSA
    from Crypto.Cipher import PKCS1_v1_5 as Cipher_PKCS1_v1_5

    class APIRequest:
        """API Request Class.

        :param context: context under which to create the API Request.
        :type context: :class:`mpesa.portalsdk.APIContext`.
        """

        def __init__(self, context=None):
            """Construct."""
            self.context = context

        def execute(self):
            """Execute API Request using ``self.context``.

            :raises: ``requests.exceptions.ConnectionError``
            :raises: ``TypeError``
            :return: response object of ``mpesa.portalsdk.APIResponse``.
            :rtype: :class:`mpesa.portalsdk.APIResponse`.
            """
            if self.context is not None:
                self.create_default_headers()
                try:
                    return {
                        APIMethodType.GET: self.__get,
                        APIMethodType.POST: self.__post,
                        APIMethodType.PUT: self.__put,
                    }.get(self.context.method_type, self.__unknown)()
                except requests.exceptions.ConnectionError as ce:
                    raise ce
            else:
                raise TypeError("Context cannot be None.")

        def create_bearer_token(self):
            """Return encrypted context api_key using the context public_key."""
            key_der = b64decode(self.context.public_key)
            key_pub = RSA.importKey(key_der)
            cipher = Cipher_PKCS1_v1_5.new(key_pub)
            cipher_text = cipher.encrypt(self.context.api_key.encode("ascii"))
            encrypted_msg = b64encode(cipher_text)

            return encrypted_msg

        def create_default_headers(self):
            """Add some default headers to ``self.context``."""
            self.context.add_header(
                "Authorization",
                "Bearer {}".format(self.create_bearer_token().decode("utf-8")),
            )
            self.context.add_header("Content-Type", "application/json")
            self.context.add_header("Host", self.context.address)

        def __get(self):
            """Return ``mpesa.portalsdk.APIResponse`` after GET Request."""
            r = requests.get(
                self.context.get_url(),
                params=self.context.get_parameters(),
                headers=self.context.get_headers(),
            )
            print(r)
            return APIResponse(
                r.status_code,
                json.loads(r.headers.__str__().replace("'", '"')),
                json.loads(r.text),
            )

        def __post(self):
            """Return ``mpesa.portalsdk.APIResponse`` after POST Request."""
            r = requests.post(
                self.context.get_url(),
                headers=self.context.get_headers(),
                json=self.context.get_parameters(),
            )
            print(r)
            return APIResponse(
                r.status_code,
                json.loads(r.headers.__str__().replace("'", '"')),
                json.loads(r.text),
            )

        def __put(self):
            """Return ``mpesa.portalsdk.APIResponse`` after PUT Request."""
            print("PUT")
            r = requests.put(
                self.context.get_url(),
                headers=self.context.get_headers(),
                json=self.context.get_parameters(),
            )
            print("PUT", r)
            return APIResponse(
                r.status_code,
                json.loads(r.headers.__str__().replace("'", '"')),
                json.loads(r.text),
            )

        def __unknown(self):
            """Raise Unknown Method Exception.

            :raises: Exception("Unknown Method").
            """
            raise Exception("Unknown Method")

    class APIResponse(dict):
        """API Response Class.

        :param status_code: String representing HTTP Status Code.
        :param headers: Dict object of key,value headers.
        :param body: Dict object of key,value pairs.
        :type status_code: str
        :type headers: dict
        :type body: dict
        """

        def __init__(self, status_code, headers, body):
            """Construct."""
            super(APIResponse, self).__init__()
            self["status_code"]: str = status_code
            self["headers"]: dict = headers
            self["body"]: dict = body

        @property
        def status_code(self) -> int:
            """Return HTTP Status Code."""
            return self["status_code"]

        @status_code.setter
        def status_code(self, status_code: int):
            if type(status_code) is not int:
                raise TypeError("status_code must be a int")
            else:
                self["status_code"] = status_code

        @property
        def headers(self) -> dict:
            """Return HTTP Headers."""
            return self["headers"]

        @headers.setter
        def headers(self, headers: dict):
            if type(headers) is not dict:
                raise TypeError("headers must be a dict")
            else:
                self["headers"] = headers

        @property
        def body(self) -> dict:
            """Return HTTP Body."""
            return self["body"]

        @body.setter
        def body(self, body: dict):
            if type(body) is not dict:
                raise TypeError("body must be a dict")
            else:
                self["body"] = body

    class APIMethodType(Enum):
        """API Method Type Class."""

        GET: int = 0
        POST: int = 1
        PUT: int = 3
        DELETE: int = 4

    class APIContext(dict):
        """API Context Class.

        :param api_key: API Key.
        :param public_key: Public Key.
        :param ssl: Whether to use ssl, defaults ``False``.
        :param method_type: HTTP Method Type, defaults ``APIMethodType.GET``.
        :param address: Address, defaults ``""``.
        :param port: Port, defaults ``80``.
        :param path: URL path, defaults ``""``.
        :param headers: Headers, defaults ``{}``.
        :param parameters: Parameters, defaults ``{}``.
        :type api_key: str.
        :type public_key: str.
        :type ssl: bool.
        :type method_type: :class: `APIMethodType`, optional.
        :type address: str.
        :type port: int.
        :type path: str.
        :type headers: dict.
        :type parameters: dict.
        """

        def __init__(
            self,
            api_key="",
            public_key="",
            ssl=False,
            method_type=APIMethodType.GET,
            address="",
            port=80,
            path="",
            headers={},
            parameters={},
        ):
            super(APIContext, self).__init__()

            self["api_key"]: str = api_key
            self["public_key"]: str = public_key
            self["ssl"]: bool = ssl
            self["method_type"]: Enum = method_type
            self["address"]: str = address
            self["port"]: int = port
            self["path"]: str = path
            self["headers"]: dict = headers
            self["parameters"]: dict = parameters

        def get_url(self):
            """Return formed url from context data."""
            if self.ssl is True:
                return "https://{}:{}{}".format(self.address, self.port, self.path)
            else:
                return "http://{}:{}{}".format(self.address, self.port, self.path)

        def add_header(self, header, value):
            """Update Headers dict with header,value."""
            self["headers"].update({header: value})

        def get_headers(self):
            """Return self headers."""
            return self["headers"]

        def add_parameter(self, key, value):
            """Update Parameters dict with key,value."""
            self["parameters"].update({key: value})

        def get_parameters(self):
            """Return self parameters."""
            return self["parameters"]

        @property
        def api_key(self) -> str:
            """Return self api_key."""
            return self["api_key"]

        @api_key.setter
        def api_key(self, api_key: str):
            if type(api_key) is not str:
                raise TypeError("api_key must be a str")
            else:
                self["api_key"] = api_key

        @property
        def public_key(self) -> str:
            """Return self public_key."""
            return self["public_key"]

        @public_key.setter
        def public_key(self, public_key: str):
            if type(public_key) is not str:
                raise TypeError("public_key must be a str")
            else:
                self["public_key"] = public_key

        @property
        def ssl(self) -> bool:
            """Return self ssl."""
            return self["ssl"]

        @ssl.setter
        def ssl(self, ssl: bool):
            if type(ssl) is not bool:
                raise TypeError("ssl must be a bool")
            else:
                self["ssl"] = ssl

        @property
        def method_type(self) -> APIMethodType:
            """Return self method_type."""
            return self["method_type"]

        @method_type.setter
        def method_type(self, method_type: APIMethodType):
            if type(method_type) is not APIMethodType:
                raise TypeError("method_type must be a APIMethodType")
            else:
                self["method_type"] = method_type

        @property
        def address(self) -> str:
            """Return self address."""
            return self["address"]

        @address.setter
        def address(self, address: str):
            if type(address) is not str:
                raise TypeError("address must be a str")
            else:
                self["address"] = address

        @property
        def port(self) -> int:
            """Return self port."""
            return self["port"]

        @port.setter
        def port(self, port: int):
            if type(port) is not int:
                raise TypeError("port must be a int")
            else:
                self["port"] = port

        @property
        def path(self) -> str:
            """Return self path."""
            return self["path"]

        @path.setter
        def path(self, path: str):
            if type(path) is not str:
                raise TypeError("path must be a str")
            else:
                self["path"] = path