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

Develop #31

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions fast_api_als/database/db_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
write a commong function that logs this response code with appropriate context data
"""

logger = logging.getLogger(__name__)

class DBHelper:
def __init__(self, session: boto3.session.Session):
Expand All @@ -25,6 +26,10 @@ def __init__(self, session: boto3.session.Session):
self.geo_data_manager = self.get_geo_data_manager()
self.dealer_table = self.ddb_resource.Table(constants.DEALER_DB_TABLE)
self.get_api_key_author("Initialize_Connection")

def log_response(res):
responseCode = res['ResponseMetadata']['HTTPStatusCode']
logger.info(f"Response Code is {responseCode}")

def get_geo_data_manager(self):
config = dynamodbgeo.GeoDataManagerConfiguration(self.session.client('dynamodb', config=botocore.client.Config(max_pool_connections=99)), constants.DEALER_DB_TABLE)
Expand Down
6 changes: 6 additions & 0 deletions fast_api_als/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import time
import logging

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
Expand Down Expand Up @@ -26,6 +27,11 @@
)


# configuring the logger
logging.basicConfig(filename="newfile.log",
format='%(asctime)s %(message)s',
filemode='w')

@app.get("/")
def root():
return {"message": "Welcome to jTU"}
Expand Down
16 changes: 9 additions & 7 deletions fast_api_als/routers/lead_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
write proper logging and exception handling
"""

logger = logging.getLogger(__name__)

def get_quicksight_data(lead_uuid, item):
"""
Creates the lead converted data for dumping into S3.
Expand All @@ -37,26 +39,27 @@ def get_quicksight_data(lead_uuid, item):
"3pl": item.get('3pl', 'unknown'),
"oem_responded": 1
}
logger.info("created lead converted data.")
return data, f"{item['make']}/1_{int(time.time())}_{lead_uuid}"


@router.post("/conversion")
async def submit(file: Request, token: str = Depends(get_token)):
body = await file.body()
body = json.loads(str(body, 'utf-8'))

logger.info("Validating payload details")
if 'lead_uuid' not in body or 'converted' not in body:
# throw proper HTTPException
pass
raise HTTPException(status_code=400, detail="Invalid request arguments.")


lead_uuid = body['lead_uuid']
converted = body['converted']

oem, role = get_user_role(token)
if role != "OEM":
# throw proper HTTPException
pass
raise HTTPException(status_code=400, detail="Invalid token details")

logger.info("Performing lead conversion update")
is_updated, item = db_helper_session.update_lead_conversion(lead_uuid, oem, converted)
if is_updated:
data, path = get_quicksight_data(lead_uuid, item)
Expand All @@ -66,5 +69,4 @@ async def submit(file: Request, token: str = Depends(get_token)):
"message": "Lead Conversion Status Update"
}
else:
# throw proper HTTPException
pass
raise HTTPException(status_code=500, detail="Updata lead conversion not happened")
65 changes: 51 additions & 14 deletions fast_api_als/routers/submit_lead.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,37 @@
router = APIRouter()

"""
Add proper logging and exception handling.
Add proper logger and exception handling.

keep in mind:
You as a developer has to find how much time each part of code takes.
you will get the idea about the part when you go through the code.
"""

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handle exceptions wherever external APIs are used in this file.

logger = logging.getLogger(__name__)


@router.post("/submit/")
async def submit(file: Request, apikey: APIKey = Depends(get_api_key)):
start = int(time.time() * 1000.0)
t1 = [int(time.time() * 1000.0)]

if not db_helper_session.verify_api_key(apikey):
# throw proper fastpi.HTTPException
pass

raise HTTPException(status_code=403, detail="Invalid api key")

body = await file.body()
body = str(body, 'utf-8')

obj = parse_xml(body)

# check if xml was not parsable, if not return
if not obj:
provider = db_helper_session.get_api_key_author(apikey)
logger.info("xml was not parsable.")
try:
provider = db_helper_session.get_api_key_author(apikey)
except Exception as e:
raise HTTPException(status_code=404, detail="API key author not found.")

obj = {
'provider': {
'service': provider
Expand All @@ -64,10 +71,12 @@ async def submit(file: Request, apikey: APIKey = Depends(get_api_key)):
lead_hash = calculate_lead_hash(obj)

# check if adf xml is valid
logger.debug("checking if xml is valid.")
validation_check, validation_code, validation_message = check_validation(obj)

#if not valid return
if not validation_check:
logger.info("xml is not valid.")
item, path = create_quicksight_data(obj['adf']['prospect'], lead_hash, 'REJECTED', validation_code, {})
s3_helper_client.put_file(item, path)
return {
Expand All @@ -77,6 +86,7 @@ async def submit(file: Request, apikey: APIKey = Depends(get_api_key)):
}

# check if vendor is available here
logger.debug("checking is vendor is available.")
dealer_available = True if obj['adf']['prospect'].get('vendor', None) else False
email, phone, last_name = get_contact_details(obj)
make = obj['adf']['prospect']['vehicle']['make']
Expand All @@ -87,26 +97,36 @@ async def submit(file: Request, apikey: APIKey = Depends(get_api_key)):

# check if 3PL is making a duplicate call or it is a duplicate lead
with ThreadPoolExecutor(max_workers=5) as executor:
futures = [executor.submit(db_helper_session.check_duplicate_api_call, lead_hash,
obj['adf']['prospect']['provider']['service']),
executor.submit(db_helper_session.check_duplicate_lead, email, phone, last_name, make, model),
executor.submit(db_helper_session.fetch_oem_data, make, True)
]
try:
futures = [executor.submit(db_helper_session.check_duplicate_api_call, lead_hash,
obj['adf']['prospect']['provider']['service']),
executor.submit(db_helper_session.check_duplicate_lead, email, phone, last_name, make, model),
executor.submit(db_helper_session.fetch_oem_data, make, True)
]
except Exception as e:
logger.error(e)
raise Exception(e)


for future in as_completed(futures):
result = future.result()
if result.get('Duplicate_Api_Call', {}).get('status', False):
logger.info("Duplicate api call")
return {
"status": f"Already {result['Duplicate_Api_Call']['response']}",
"message": "Duplicate Api Call"
}
if result.get('Duplicate_Lead', False):
logger.info("Duplicate lead")
return {
"status": "REJECTED",
"code": "12_DUPLICATE",
"message": "This is a duplicate lead"
}
if "fetch_oem_data" in result:
fetched_oem_data = result['fetch_oem_data']


if fetched_oem_data == {}:
return {
"status": "REJECTED",
Expand All @@ -121,15 +141,23 @@ async def submit(file: Request, apikey: APIKey = Depends(get_api_key)):
}
oem_threshold = float(fetched_oem_data['threshold'])


# if dealer is not available then find nearest dealer
logger.debug("finding the nearest dealer")
if not dealer_available:
lat, lon = get_customer_coordinate(obj['adf']['prospect']['customer']['contact']['address']['postalcode'])
nearest_vendor = db_helper_session.fetch_nearest_dealer(oem=make,
lat=lat,
lon=lon)
try:
nearest_vendor = db_helper_session.fetch_nearest_dealer(oem=make,
lat=lat,
lon=lon)
except Exception as e:
logger.error(e)
raise Exception(e)

obj['adf']['prospect']['vendor'] = nearest_vendor
dealer_available = True if nearest_vendor != {} else False

logger.info("processing the lead")
# enrich the lead
model_input = get_enriched_lead_json(obj)

Expand All @@ -140,6 +168,7 @@ async def submit(file: Request, apikey: APIKey = Depends(get_api_key)):
result = score_ml_input(ml_input, make, dealer_available)

# create the response
logger.info("creating the response")
response_body = {}
if result >= oem_threshold:
response_body["status"] = "ACCEPTED"
Expand All @@ -158,10 +187,16 @@ async def submit(file: Request, apikey: APIKey = Depends(get_api_key)):
lead_uuid = str(uuid.uuid5(uuid.NAMESPACE_URL, email + phone + last_name + make + model))
item, path = create_quicksight_data(obj['adf']['prospect'], lead_uuid, response_body['status'],
response_body['code'], model_input)

# insert the lead into ddb with oem & customer details
# delegate inserts to sqs queue
if response_body['status'] == 'ACCEPTED':
make_model_filter = db_helper_session.get_make_model_filter_status(make)
logger.info("status is accepted, generating the response body")
try:
make_model_filter = db_helper_session.get_make_model_filter_status(make)
except Exception as e:
logger.error(e)
raise Exception(e)
message = {
'put_file': {
'item': item,
Expand Down Expand Up @@ -199,6 +234,7 @@ async def submit(file: Request, apikey: APIKey = Depends(get_api_key)):
res = sqs_helper_session.send_message(message)

else:
logger.info("status is not accepted")
message = {
'put_file': {
'item': item,
Expand All @@ -214,5 +250,6 @@ async def submit(file: Request, apikey: APIKey = Depends(get_api_key)):
time_taken = (int(time.time() * 1000.0) - start)

response_message = f"{result} Response Time : {time_taken} ms"
logger.info("Response body generated. Returning it.")

return response_body
14 changes: 12 additions & 2 deletions fast_api_als/services/enrich_lead.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,17 @@
"""
what exceptions can be thrown here?
"""

logger = logging.getLogger(__name__)

def get_enriched_lead_json(adf_json: dict) -> dict:
pass
try:
# process the dict

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is handled here? you can handle keyerror and valueerror in this



except KeyError as ke:
logger.error("Key not found in adf_json", ke)
except ValueError as ve:
logger.error("Key can't take this value", ve)



35 changes: 33 additions & 2 deletions fast_api_als/services/verify_phone_and_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@
You also trying to undderstand the execution time factor.
"""

# creating a logger
logger = logging.getLogger(__name__)

async def call_validation_service(url: str, topic: str, value: str, data: dict) -> None: # 2
if value == '':
logger.info("Value was empty.")
return
logger.info("Calling validation service")
async with httpx.AsyncClient() as client: # 3

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exception handling missing

response = await client.get(url)

r = response.json()
logger.debug("Updating the topic in data to json response")
data[topic] = r


Expand All @@ -29,23 +35,48 @@ async def verify_phone_and_email(email: str, phone_number: str) -> bool:
ALS_DATA_TOOL_EMAIL_VERIFY_METHOD,
ALS_DATA_TOOL_REQUEST_KEY,
email)

logger.debug("Email validation url generated.")

phone_validation_url = '{}?Method={}&RequestKey={}&PhoneNumber={}&OutputFormat=json'.format(
ALS_DATA_TOOL_SERVICE_URL,
ALS_DATA_TOOL_PHONE_VERIFY_METHOD,
ALS_DATA_TOOL_REQUEST_KEY,
phone_number)

logger.debug("Phone validation url generated.")

email_valid = False
phone_valid = False
data = {}

await asyncio.gather(
call_validation_service(email_validation_url, "email", email, data),
call_validation_service(phone_validation_url, "phone", phone_number, data),
logger.info("Calling email validation service.")
try:
call_validation_service(email_validation_url, "email", email, data),
except Exception as e:
logger.error("Error in calling email_validation_service.")
logger.info(e)

logger.info("Email validation service called.")
logger.info("Calling phone number validation service.")
try:
call_validation_service(phone_validation_url, "phone", phone_number, data),
except Exception as e:
logger.error("Error in calling call_validation_service.")
logger.info(e)

logger.info("Phone validation service called.")
)

if "email" in data:
if data["email"]["DtResponse"]["Result"][0]["StatusCode"] in ("0", "1"):
logger.info("Email validated")
email_valid = True
if "phone" in data:
if data["phone"]["DtResponse"]["Result"][0]["IsValid"] == "True":
logger.info("Phone number validated")
phone_valid = True

logger.debug("Returning the response : whether email is valid or phone number is valid")
return email_valid | phone_valid