from dotenv import load_dotenv # used to load in api keys for users from hidden .env file
import os # for accessing api key
import requests # for accessing json from openFDA drug/label API
[docs]
class Drug_Label_Client:
"""
OVERVIEW:
The Label Client is an API client class that allows an individual to
engage directly with the openFDA Drug/Label API after loading in
their API key. Users can use generic_search and search_request in
tandem to expedite search-term specific API calls, or use
manual_request for non-Drug/label search-specific queries of the
broader openFDA API environment.
ATTRIBUTES
api_key (str): OpenFDA API key loaded in by user
base_url (str): Base URL for OpenFDA endpoint (shortcut functions)
session (requests.Session()): Universal HTTP sesh for API requests
USAGE EXAMPLE:
>>> # User function loads in API key as api_key
>>> client = Drug_Label_Client(api_key)
>>> advil_items = generic_search("brand_name", "Advil")
>>> advil_json = search_request(advil_items, limit=1)
>>> print(advil_json) # prints raw complete json for shortcut query
"""
def __init__(self, api_key: str):
"""
OVERVIEW:
Initializes the Drug_Label_Client by starting a universal API
session, stores the user api_key, builds openFDA url base.
PARAMETERS:
api_key (str): takes in string (required format) of user key
RETURN VALUE:
None
USAGE EXAMPLE:
>>> # User function loads in API key as api_key
>>> client = Drug_Label_Client(api_key) # executes __init__
"""
# start with type error test to ensure string value for api_key
if not isinstance(api_key, str):
raise TypeError(f"The API key must be a string, got {type(api_key).__name__}")
# check for an empty string
if not api_key.strip():
raise ValueError("The API key can't be empty")
# After passing, store api_key, base_url, and initialize the requests Session
self.api_key = api_key
self.base_url = f"https://api.fda.gov/drug/label.json"
self.session = requests.Session()
[docs]
def search_request(self, search_items,limit: int = 1):
"""
OVERVIEW:
Executes a search based on an inputted list of search_items
which contains a list of searches built by generic_search.
Returns a raw json with the number of searches equal to limit.
PARAMETERS:
search_items (list): list of fields generated by generic_search
limit (int): defines the number of total drugs contained in json
RETURN VALUE:
returns response.json(), raw json format of openFDA HTTPS request
USAGE EXAMPLE:
>>> client = Drug_Label_Client(api_key)
>>> client.search_request([generic_search("brand_name","Advil")])
>>> # above gives json format of first item search for "Advil"
"""
# Ensure correct search_items type and non-empty
if not isinstance(search_items, list):
raise TypeError(f"The search_items must be a list of generic_search generated strings")
if not search_items:
raise ValueError("search_items cannot be empty.")
for item in search_items:
if not isinstance(item, str):
raise TypeError(f"The items in search_items must be generic_search generated strings")
# Ensure correct limit type:
if isinstance(limit, bool):
raise TypeError("The limit parameter must be an integer, not a boolean.")
if not isinstance(limit, int):
raise TypeError("The limit parameter must be an integer.")
if limit < 1:
raise ValueError("The limit parameter must be at least 1.")
# Combine all search_items generated into a singular string to embed with our self.base_url
combined_search = "+AND+".join(search_items)
# construct the complete url
url = f"{self.base_url}?api_key={self.api_key}&search={combined_search}&sort=effective_time:desc&limit={limit}"
# test the url
try:
response = self.session.get(url)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise
[docs]
def generic_search (self, parameter, name, exact=False):
"""
OVERVIEW:
Generates a generic_search string for input into the search_request
function. Takes in a parameter (of any drug) and a particular name
for the parameter to search for. For example, searching by parameter
"brand_name" and name "Advil" yields all drugs with "Advil" in the
"brand_name" field. Exact allows for exact string matching in the
search field, and defaults to false.
Multiple generic_search functions can be combined using +AND+ and
this is the reason for including all generic_search items in a list
when inputting into search_request.
PARAMETERS:
parameter (str): field search function is being performed on
name (str): string to match parameter search function to
exact (Bool): designates whether exact match is required
RETURN VALUE:
search_item (str): string formatted for link insertion
USAGE EXAMPLE:
>>> client = Drug_Label_Client(api_key)
>>> client.generic_search("brand_name","Advil")
>>> # above returns list of drugs w/ "brand_name" containing "Advil"
"""
# Verify types for all input parameters
if not isinstance(parameter, str):
raise TypeError(f"parameter must be a string")
if not isinstance(name, str):
raise TypeError(f"name must be a string")
if not isinstance(exact, bool):
raise TypeError(f"exact must be a Boolean.")
# Based on exact parameter, create link with parameter and name and return for use in search_request function
if exact==False:
search_item = f"openfda.{parameter}:\"{name}\""
return search_item
elif exact==True:
search_item = f"openfda.{parameter}.exact:\"{name}\""
return search_item
[docs]
def manual_request(self, url):
"""
OVERVIEW:
Executes a manual data pull request using a user-provided url.
PARAMETERS:
url (str): string of user-inputted url.
RETURN VALUE:
response.json() => json response if valid url is enterred.
USAGE EXAMPLE:
>>> client = Drug_Label_Client(api_key)
>>> url = # [user inputted string]
>>> client.manual_request(url)
"""
# url checks
if not isinstance(url, str):
raise TypeError(f"url inputted must be a string")
if not url.strip():
raise ValueError("url cannot be empty")
# attempted query
try:
response = self.session.get(url)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise