I'm coming from PHP and recently started developing in Python. As a part of my self-study course, I developed a small library for access to Visa Developer Program APIs and published it on Github. It is bundled with demo Django app. Here I'm sharing a module that implements an abstract connection to VDP.
I'd appreciate hearing your comments regarding the source code. Personally, I have a feeling that modules hierarchy could have been organized differently and maybe I'm overusing OOP approaches.
visa/request.py
import jsonpickle
import os
import requests
try:
import urllib.parse as urllib_parse
except ImportError:
# Python 2.7 compatibility
from urllib import parse as urllib_parse
try:
import configparser as parser
except ImportError:
import ConfigParser as parser
from visa.exceptions import (VisaAccessDeniedError, VisaDuplicateTransactionError, VisaGeneralError,
VisaMessageValidationError, VisaNotFoundError, VisaTimeoutError, VisaUnauthenticatedError)
class VisaRequest:
"""
Constructs a request to Visa API using provided transaction, resource, method and http verb.
Request can be submitted with request() method, which returns a dictionary in following format:
{'code': http_response_code, 'message': json-encoded content}
This class is not assumed to be instantiated on its own (consider it abstract). Instead, every VISA API
implementation must provide its own request class, inheriting from VisaRequest and providing a corresponding VISA
API resource name as a `resource` argument value.
"""
config = parser.ConfigParser()
config_file = os.path.join(os.path.dirname(__file__), 'configuration.ini')
config.read(config_file)
def __init__(self, resource, api, method, http_verb):
"""
:param resource: VISA API resource name
:type resource: str
:param api: VISA API api name
:type api: str
:param method: VISA API endpoint method
:type method: str
:param http_verb: VISA API HTTP verb
:type http_verb: str
"""
url = self.config.get('VISA', 'url')
version = self.config.get('VISA', 'version')
self.user_id = self.config.get('VISA', 'username')
self.password = self.config.get('VISA', 'password')
self.cert = os.path.join(os.path.dirname(__file__), self.config.get('VISA', 'cert'))
self.key = os.path.join(os.path.dirname(__file__), self.config.get('VISA', 'key'))
self.api_endpoint = url + "/" + resource + "/" + api + "/" + version + "/" + method
self.http_verb = http_verb
# FIXME: Hardcoded request/response in json format
self.headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
self.result = None
def send(self, transaction=None):
"""
Sends a Transaction object to VISA using self.api_endpoint field and corresponding http verb.
:param transaction: instance of VisaTransaction
:type transaction: VisaTransaction
:return: dict
"""
transaction = self.__to_vd(transaction)
result = requests.request(
self.http_verb,
self.api_endpoint,
data=transaction,
cert=(self.cert, self.key),
auth=(self.user_id, self.password),
headers=self.headers
)
return self.__response({'code': result.status_code, 'message': result.json()})
@staticmethod
def __response(result):
"""
Processes a response from Visa Direct API.
Depending on http code in response, either returns a result or raises corresponding app-level exception.
:param result: Response from VISA
:type result: dict
:return result: Resulting dictionary
:raises VisaTimeoutError, VisaDuplicateTransactionError, VisaMessageValidationError, VisaUnauthenticatedError,
VisaAccessDeniedError, VisaNotFoundError, VisaGeneralError
"""
code = result['code']
if code == 200:
return result
elif code == 202:
raise VisaTimeoutError(result=result)
elif code == 303:
raise VisaDuplicateTransactionError(result=result)
elif code == 400:
raise VisaMessageValidationError(result=result)
elif code == 401:
result = {'message': {'errorMessage': str(result['message'])}}
raise VisaUnauthenticatedError(result=result)
elif code == 403:
raise VisaAccessDeniedError(result=result)
elif code == 404:
raise VisaNotFoundError(result=result)
else:
raise VisaGeneralError(result=result)
@staticmethod
def __to_vd(transaction=None):
"""
Serializes VisaTransaction object to json string.
:param transaction: instance of VisaTransaction.
:return: json
"""
if not transaction:
return
else:
return jsonpickle.encode(transaction, unpicklable=False)