Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Senseloaf Resume Parser Provider #69

Merged
merged 1 commit into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions edenai_apis/api_keys/senseloaf_settings_template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"api_key": "",
"email": "",
"password": "",
"comment": "You can either set your API Key, or your Login Credentials (Email and Password) for Authentication"
}

1 change: 1 addition & 0 deletions edenai_apis/apis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,6 @@
from .winstonai import WinstonaiApi
from .vernai import VernaiApi
from .readyredact import ReadyRedactApi
from .senseloaf import SenseloafApi

# THIS NEEDS TO BE DONE AUTOMATICALLY
1 change: 1 addition & 0 deletions edenai_apis/apis/senseloaf/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .senseloaf_api import SenseloafApi
352 changes: 352 additions & 0 deletions edenai_apis/apis/senseloaf/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,352 @@
from edenai_apis.utils.http import HTTPMethod
from edenai_apis.utils.exception import ProviderException
import requests
from enum import Enum
from typing import Optional
from http import HTTPStatus
import json
from json import JSONDecodeError
import tempfile, os, mimetypes

from .models import ResponseData

class Parser(Enum):

RESUME = 'resume'
JD = 'job_description'
INVOICE = 'invoice' # Coming Soon
RECEIPT = 'receipt' # Coming Soon
INDOID = 'indonesian_id' # Coming Soon
AADHAAR = 'aadhaar' # Coming Soon



class Client:

BASE_URL = 'https://service.senseloaf.com'
__api_key: Optional[str]
__last_api_response: Optional[dict]
__last_api_response_type: Optional[str]
__last_api_response_code: Optional[str]

def __init__(
self,
api_key: Optional[str] = None,
email: Optional[str] = None,
password: Optional[str] = None
) -> None:
if all([i == '' for i in [api_key, email, password]]):
raise ProviderException('Please provide api_key or email and password for authentication')
if api_key == '' and (email == '' or password == ''):
raise ProviderException('Please provide both email and password for authentication')
if api_key:
self.__api_key = api_key.replace('Bearer ', '')
else:
self.__login(email, password)


def __request(
self,
method: HTTPMethod,
url: str,
data: Optional[dict] = None,
headers: Optional[dict] = None,
json_field: Optional[dict] = None,
files: Optional[dict] = None,
params: Optional[dict] = None,
return_type: Optional[str] = 'json'
) -> ResponseData:
response: requests.Response = requests.request(
method=method.value,
url=url,
data=data,
params=params,
files=files,
headers=headers,
json=json_field,
)

try:
response.raise_for_status()

if response.status_code == HTTPStatus.NO_CONTENT:
return ResponseData(
response={},
response_code=str(response.status_code),
response_type='json'
)

if return_type == 'headers':
self.__last_api_response = response.headers
self.__last_api_response_type = 'headers'
self.__last_api_response_code = str(response.status_code)
return ResponseData(
response=response.headers,
response_code=str(response.status_code),
response_type='headers'
)
elif return_type == 'content':
self.__last_api_response = response.content
self.__last_api_response_type = 'content'
self.__last_api_response_code = str(response.status_code)
return ResponseData(
response={'content': response.content},
response_code=str(response.status_code),
response_type='content'
)
elif return_type == 'text':
self.__last_api_response = response.text
self.__last_api_response_type = 'text'
self.__last_api_response_code = str(response.status_code)
return ResponseData(
response={'text': response.text},
response_code=str(response.status_code),
response_type='text'
)
else:
self.__last_api_response = response.json()
self.__last_api_response_type = 'json'
self.__last_api_response_code = str(response.status_code)
return ResponseData(
response=response.json(),
response_code=str(response.status_code),
response_type='json'
)
except requests.exceptions.HTTPError as exc:
message = json.loads(exc.response.text)
if message.get('errorCode') == 'AUTHENTICATION_FAILED':
message['errorMessage'] = 'Authentication Failed. Please check your EmailID/Password Combination or API Key'
message['response']['error']['faultDetail'] = ['Authentication Failed. Please check your EmailID/Password Combination or API Key']
else:
message = exc.response.text
raise ProviderException(
message=f"{exc}\nError message: {message}",
code=str(response.status_code),
) from exc
except JSONDecodeError:
raise ProviderException(
message="Internal server error", code=str(response.status_code)
)


def __login(self, email, password):
url = f"{self.BASE_URL}/login"
headers = {
"Content-Type": "application/json",
}
payload = json.dumps({
"emailId": email,
"password": password
})
response = self.__request(
method=HTTPMethod.POST,
url=url,
data=payload,
headers=headers,
json_field=None,
files=None,
params=None,
return_type='headers'
)
self.__api_key = response.response.get('Authorization').replace("Bearer ", '')


def __parse_resume_from_file(
self,
file: str
) -> ResponseData:
url = f'{self.BASE_URL}/api/v2/parse-resume'
files = [
('files',(file.split('/')[-1], open(file,'rb'),mimetypes.guess_type(file)[0]))
]
headers = {
'Authorization': f'Bearer {self.__api_key}'
}
response = self.__request(
method=HTTPMethod.POST,
url=url,
data=None,
headers=headers,
json_field=None,
files=files,
params=None,
return_type='json'
)
if response.response_code != '200':
raise ProviderException(
message=response.response,
code=response.response_code
)

else:
errors = response.response.get('errors', [])
if len(errors) > 0:
raise ProviderException(
message=errors[0],
code=response.response_code
)
else:
return response


def __parse_resume_from_url(
self,
url: str
) -> ResponseData:
parser_url = f'{self.BASE_URL}/api/v2/parse-resume-url'
data = json.dumps({
'resumeUrl': url
})
headers = {
'Authorization': f'Bearer {self.__api_key}'
}
response = self.__request(
method=HTTPMethod.POST,
url=parser_url,
data=data,
headers=headers,
json_field=None,
files=None,
params=None,
return_type='json'
)
if str(response.response_code) != 200:
raise ProviderException(
message=response.response.get('message'),
code=response.response_code
)

else:
errors = response.response.get('errors', [])
if len(errors) > 0:
raise ProviderException(
message=errors[0].get('message'),
code=errors[0].get('code')
)
else:
return response


def __parse_jd_from_file(
self,
file: str
) -> ResponseData:
url = f'{self.BASE_URL}/api/parse-jd'
files = [
('files',(file, open(file,'rb'),'application/pdf'))
]
headers = {
'Authorization': f'Bearer {self.__api_key}'
}
response = self.__request(
method=HTTPMethod.POST,
url=url,
data=None,
headers=headers,
json_field=None,
files=files,
params=None,
return_type='json'
)
if str(response.response_code) != 200:
raise ProviderException(
message=response.response.get('message'),
code=response.response_code
)
else:
errors = response.response.get('errors', [])
if len(errors) > 0:
raise ProviderException(
message=errors[0].get('message'),
code=errors[0].get('code')
)
else:
return response


def __parse_jd_from_url(
self,
url: str
) -> ResponseData:
tempdir = tempfile.gettempdir()
filename = url.split('/')[-1]
filepath = os.path.join(tempdir, filename)
with open(filepath, 'wb') as f:
f.write(requests.get(url).content)
return self.__parse_jd_from_file(filepath)


def __parse_resume(
self,
file: Optional[str] = '',
url: Optional[str] = ''
) -> ResponseData:
if file == '' and url == '':
raise ProviderException('Please provide path to file or url for parsing resume')
elif file != '' and url != '':
raise ProviderException('Please provide file or url for parsing resume, not both')
elif file != '':
return self.__parse_resume_from_file(file)
elif url != '':
return self.__parse_resume_from_url(url)



def __parse_jd(
self,
file: Optional[str] = None,
url: Optional[str] = None
):
if file == '' and url == '':
raise ProviderException('Please provide path to file or url for parsing resume')
elif file != '' and url != '':
raise ProviderException('Please provide file or url for parsing resume, not both')
elif file != '':
return self.__parse_jd_from_file(file)
elif url != '':
return self.__parse_jd_from_url(url)


def parse_document(
self,
parse_type: Parser,
file: Optional[str] = '',
url: Optional[str] = ''
) -> ResponseData:
if parse_type.value == 'resume':
return self.__parse_resume(file, url)
elif parse_type.value == 'job_description':
return self.__parse_jd(file, url)
elif parse_type.value == 'invoice':
raise NotImplementedError(
'Invoice parsing is not implemented yet. Reach out to us at team@senseloaf.com for requesting early release'
)
elif parse_type.value == 'receipt':
raise NotImplementedError(
'Receipt parsing is not implemented yet. Reach out to us at team@senseloaf.com for requesting early release'
)
elif parse_type.value == 'indonesian_id':
raise NotImplementedError(
'Indonesian ID card parsing is not implemented yet. Reach out to us at team@senseloaf.com for requesting early release'
)
elif parse_type.value == 'aadhaar':
raise NotImplementedError(
'Indian Aadhaar card parsing is not implemented yet. Reach out to us at team@senseloaf.com for requesting early release'
)
else:
raise NotImplementedError(
f'Parsing type {parse_type} is not implemented yet. Reach out to us at team@senseloaf.com for requesting early release'
)

@property
def cache(self):
return ResponseData(
response = self.__last_api_response,
response_code = self.__last_api_response_code,
response_type = self.__last_api_response_type
)

def clear_cache(self):
self.__last_api_response = {}
self.__last_api_response_code = ''
self.__last_api_response_type = ''
8 changes: 8 additions & 0 deletions edenai_apis/apis/senseloaf/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from edenai_apis.utils.exception import (
ProviderErrorLists,
ProviderInternalServerError
)

ERRORS: ProviderErrorLists = {
ProviderInternalServerError : [r".*Internal server error.*"],
}
Loading
Loading