Thanks in advance if you are reading this code.
I recently submitted this code as part of an interview (took about 4-5 hours). Unfortunately, they did not like the code and I received a form rejection email without any sort of feedback. However, I am committed to improving my code and I would like to learn from my mistakes. The code below works. You should be able to run it yourself. It takes about 2 minutes to run. Access to the database is there. It is a test database, but I do not maintain it. It is perfectly fine to have the username and password there.
What the code does: The code accesses an API and a database. It then looks for people with the same first and last name and matches them up and extracts if they were active within 30 days: on the database and on the API, which each represent a different user platform. There was a space constraint for this assignment, which is why I used generators. There is some stuff I didn't mention, but this is the meat of the assignment. Please let me know if any additional clarification is required.
I thought that I had done a pretty good job, but apparently not. Please let me know if you have any feedback (positive and critical) on this code and how it could be improved (assuming it does what it's supposed to do). I would really like to be able to take my rejection and turn it into a learning opportunity. Thanks again.
If you feel that you need to contact me, let me know and we can work it out.
import time
import requests
import pymysql
from datetime import datetime, date
import json
#
# HELPER FUNCTIONS
#
def database_endpoint_iterator(database_config, database_query, size):
"""Generator function that connects to a database and iterates over the data.
Parameters:
database_config (dict): Configuration details for database.
database_query (str): Query specifying what information to extract from the database.
size (int): Number of rows to fetch each time. Controls how much data is loaded at one time into memory.
"""
connection = pymysql.connect(**database_config)
cursor = connection.cursor(pymysql.cursors.DictCursor)
cursor.execute(database_query)
while True:
rows = cursor.fetchmany(size)
if not rows:
break
for row in rows:
yield row
connection.close()
def api_endpoint_iterator(endpoint_url, page_size):
"""Generator function that queries a REST API and iterates over paginated data.
Parameters:
endpoint_url (str): REST API url.
page_size (int): Number of pages to fetch each time. Controls how much data is loaded at one time into memory.
"""
page = 1
total_pages = 1
users_left_over = []
while True:
users = users_left_over
# fetches correct amount of pages at one time
for _ in range(page_size):
payload = {
'page': page
}
r = requests.get(endpoint_url, params=payload)
r_json = r.json()
total_pages = r_json['total_pages']
users += r_json['users']
if page > total_pages:
break
page += 1
# users are only sorted by last name, this ensures that users are sorted by last name and first name
users.sort(key=lambda user: (user['lastname'], user['firstname']))
# handles situations where users with the same last name span multiple pages
for index, user in enumerate(users):
if user['lastname'] == users[-1]['lastname']:
users_left_over = users[index:]
break
yield user
if page > total_pages:
break
# gets any users that were left over due to same last names spanning multiple pages
for user in users_left_over:
yield user
def compare(user1, user2):
"""Compares two users using their first name and last name.
Returns:
0 if users have the same first name and last name
1 if user1 comes alphabetically after user2
-1 if user1 comes alphabetically before user2
"""
user1_str = user1['lastname'] + ' ' + user1['firstname']
user2_str = user2['lastname'] + ' ' + user2['firstname']
if user1_str < user2_str:
return -1
elif user1_str > user2_str:
return 1
else:
return 0
def is_active(user):
"""Determines if a user is active.
Returns:
True if the user was active within the last 30 days, otherwise False.
"""
today = "2017-02-02"
today = datetime.strptime(today, "%Y-%m-%d")
last_active = datetime.strptime(str(user['last_active_date']), "%Y-%m-%d")
return (today - last_active).days <= 30
def create_user_dict(user_internal, user_external):
"""Creates a combined data set from an internal user and external user.
Returns:
A dictionary of relevant data for the users.
"""
user = {'firstname': user_internal['firstname'],
'lastname': user_internal['lastname'],
'specialty': user_internal['specialty'].lower(),
'practice_location': user_external['practice_location'],
'platform_registered_on': user_internal['platform_registered_on'].lower(),
'internal_classification': user_internal['classification'].lower(),
'external_classification': user_external['user_type_classification'].lower(),
'is_active_internal_platform': is_active(user_internal),
'is_active_external_platform': is_active(user_external)}
return user
#
# CONFIGURATION
#
start_time = time.time()
row_size = 5000 # configuration variable for how many rows from the database are loaded into memory
page_size = 1 # configuration variable for how many pages from the api are loaded into memory
warehouse_sample_user_count = 10
warehouse_sample = {'users': []}
total_matches = 0
# rest api url
endpoint_url = 'http://de-tech-challenge-api.herokuapp.com/api/v1/users'
# database configuration
database_config = {'host': 'candidate-coding-challenge.dox.pub',
'user': 'de_candidate',
'password': 'P8MWmPPBLhhLX79n',
'port': 3316,
'database': 'data_engineer'}
database_query = "SELECT * FROM user ORDER BY lastname, firstname;"
#
# MAIN PROGRAM
#
# set up the data iterators using the function generators
users_internal_source = database_endpoint_iterator(database_config, database_query, row_size)
users_external_source = api_endpoint_iterator(endpoint_url, page_size)
# get a user from each data source
user_internal = next(users_internal_source)
user_external = next(users_external_source)
# compare each user in one data source to the other, stop when there is no more data
while True:
try:
if compare(user_internal, user_external) == 0:
total_matches += 1
if warehouse_sample_user_count > 0:
warehouse_sample['users'].append(create_user_dict(user_internal, user_external))
warehouse_sample_user_count -= 1
user_internal = next(users_internal_source)
user_external = next(users_external_source)
elif compare(user_internal, user_external) < 0:
user_internal = next(users_internal_source)
else:
user_external = next(users_external_source)
except StopIteration:
break
# sample user data in json for the warehouse
warehouse_sample = json.dumps(warehouse_sample, indent = 4)
# sql for the design of a table that would house the results, this is just for printing to the output.txt file
sql_ddl = '''CREATE TABLE user_active_status (
id INT NOT NULL AUTO_INCREMENT,
first_name VARCHAR(50),
last_name VARCHAR(50),
specialty VARCHAR(50),
practice_location VARCHAR(50),
platform_registered_on VARCHAR(25),
internal_classification VARCHAR(50),
external_classification VARCHAR(50),
is_active_internal_platform TINYINT(1),
is_active_external_platform TINYINT(1)
PRIMARY KEY (id)
);'''
end_time = time.time()
elapsed_time = round(end_time - start_time)
#
# OUTPUT
#
# generate the output.txt file
with open("output.txt", "w") as f:
f.write("Elapsed Time: " + str(int(elapsed_time / 60)) + ' minutes, ' + str(elapsed_time % 60) + ' seconds\n\n')
f.write("Total Matches: " + str(total_matches) + "\n\n")
f.write("Sample Output:\n" + warehouse_sample + "\n\n")
f.write("SQL DDL:\n")
f.write(sql_ddl)
CREATE TABLE
in a SQL statement but don't see anything in your problem description that says it is necessary. \$\endgroup\$xxxxxxx
or something similar. \$\endgroup\$