Skip to content

Instantly share code, notes, and snippets.

@vr-greycube
Last active September 25, 2025 12:44
Show Gist options
  • Select an option

  • Save vr-greycube/5bcf24f12e731782e2abd6e014367ada to your computer and use it in GitHub Desktop.

Select an option

Save vr-greycube/5bcf24f12e731782e2abd6e014367ada to your computer and use it in GitHub Desktop.
Pattern for making api calls with oauth token
class APIClient:
"""
get access token and cache for expiry time.
"""
ACCESS_TOKEN_KEY = "API_ACCESS_TOKEN_KEY"
def __init__(self) -> None:
self.settings = frappe.get_doc("API Client Settings")
self.mfc_api_client_id = self.settings.mfc_api_client_id
self.mfc_api_client_secret = self.settings.mfc_api_client_secret
# urls
self.mfc_api_url = self.settings.mfc_api_url
self.mfc_api_oauth_url = self.settings.mfc_api_oauth_url
self.__initialize_auth()
def __initialize_auth(self):
"""Initialize and setup authentication details"""
self.access_token = frappe.cache().get_value(self.ACCESS_TOKEN_KEY)
if not self.access_token:
self.access_token = self.get_auth_token()
self.headers = {"Authorization": f"Bearer {self.access_token}"}
def renew_token(self):
if not self.access_token:
self.get_auth_token()
def get_auth_token(self):
try:
response = requests.request(
url=self.mfc_api_oauth_url + "oauth/token",
method="POST",
json={
"grant_type": "client_credentials",
"client_id": self.mfc_api_client_id,
"client_secret": self.mfc_api_client_secret,
},
headers={
"Content-Type": "application/json",
},
)
data = frappe._dict(response.json())
frappe.cache().set_value(
self.ACCESS_TOKEN_KEY,
data.access_token,
expires_in_sec=cint(data.expires_in - 300),
)
return data.access_token
except Exception:
frappe.log_error(
title="MFC API connection failed.",
message=frappe.get_traceback(),
)
def make_api_request(
self,
method,
url,
headers=None,
json_data=None,
files=None,
success_codes=(200, 201),
service_name=None,
log_args=None,
):
"""
Wrapper around requests with consistent error handling & logging.
Always includes self.headers (auth), merged with any extra headers.
"""
error, result, request_doc = None, {}, None
# Merge self.headers + passed headers
merged_headers = {**self.headers, **(headers or {})}
try:
response = requests.request(
method=method,
url=url,
headers=merged_headers,
json=json_data,
files=files,
)
if response.status_code in success_codes:
result = response.json()
else:
if response.content:
try:
error = response.content.decode("utf-8", errors="replace")
except Exception:
error = str(response.content)
else:
error = f"status_code: {response.status_code}"
frappe.log_error(
title=f"API request failed.",
message=f"Status: {response.status_code} \n\nUrl: {url} \n\nError: {error}",
)
except Exception:
error = frappe.get_traceback()
if log_args and not isinstance(log_args, dict):
log_args = dict({
"args" : str(log_args)
})
if log_args or files:
request_doc = create_request_log(
log_args,
service_name=service_name,
error=error,
output=result,
is_remote_request=1,
url=url,
status="Failed" if error else "Completed",
)
if files and request_doc:
modified_date = get_datetime()
for fieldname, filetuple in files.items():
filename, filedata, mimetype = filetuple
frappe.get_doc(
{
"doctype": "File",
"file_name": f"{modified_date}-{filename}",
"content": filedata.read()
if hasattr(filedata, "read")
else filedata,
"is_private": True,
"attached_to_doctype": "Integration Request",
"attached_to_name": request_doc.name,
}
).insert()
return result, error
# Using client in API request
def import_contract():
contract_args = {
"investorId": investor_id,
"investorType": "NaturalPerson", # other types may apply
"productExternalId": item.mfc_project_id,
"quantity": so_doc.items[0].qty,
"pricePerUnit": item.nominal_value,
"totalValue": so_doc.net_total,
"orderCreatedDate": created_date,
"externalId": so_doc.concedus_key_cf,
"documents": documents_args,
}
client = APIClient()
ENDPOINT_URL = client.mfc_api_url + "api/v1/orders"
result, error = client.make_api_request(
"POST",
ENDPOINT_URL,
json_data=contract_args,
success_codes=(201,),
service_name="API Import Contract.",
log_args=contract_args,
)
return result
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment