├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── rxclass_api ├── RxAPIWrapper.py ├── RxClassHelpers.py └── __init__.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.p 3 | *.idea 4 | /__pycache__ 5 | *.pyc 6 | /dist/ 7 | /*.egg-info -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at ianphorsman@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ian Horsman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

RxClass API Wrapper with Helper Utility

2 | 3 |

Getting Started

4 |

5 | This project contains a python wrapper for the RxClass API as well as a set of helper functions to 6 | obtain useful information stripped of unimportant data. 7 |

8 |

Helper Functions

9 |

10 | `RxClassHelpers` contains several high level helper functions to acquaint yourself with 11 | what the RxClass API can (and can't) do. Just create an instance to get started. 12 | Warning: there are a lot of false negative results. 13 |

14 | 15 | ``` 16 | 17 | pip install rxclass 18 | 19 | ``` 20 | 21 | ```python 22 | 23 | # To access API helpers: 24 | 25 | from rxclass_api import RxClassHelpers 26 | 27 | # To access API wrapper functions directly: 28 | from rxclass_api import RxAPIWrapper 29 | 30 | # To start using available helper functions create a helper instance. 31 | 32 | helper = RxClassHelpers() 33 | 34 | ``` 35 | 36 |

Supplying a with statement will also automatically load and save gathered data given a filename.

37 | 38 | ```python 39 | 40 | helper = RxClassHelpers(filename='data') 41 | with helper: 42 | ... 43 | 44 | ``` 45 | 46 |

Obtaining Class's Id and Type

47 |

Unique identifiers are represented as classId(s). Every class in RxClass has a classId and classType.

48 | 49 | ```python 50 | 51 | helper.get_class_by_name('fluoxetine') 52 | 53 | #=> 54 | {'classId': 'N0000007101', 55 | 'className': 'Fluoxetine', 56 | 'classType': 'CHEM'} 57 | 58 | helper.get_class_by_name('SSRI') 59 | 60 | #=> 61 | {'classId': 'N0000175696', 62 | 'className': 'Serotonin Reuptake Inhibitor', 63 | 'classType': 'EPC'} 64 | 65 | helper.get_class_by_name('Drug Hypersensitivity') 66 | 67 | #=> 68 | {'classId': 'N0000000999', 69 | 'className': 'Drug Hypersensitivity', 70 | 'classType': 'DISEASE'} 71 | 72 | ``` 73 | 74 |

Class Types

75 |

List all the class types with descriptions.

76 | 77 | ```python 78 | 79 | helper.list_class_types() 80 | 81 | #=> 82 | ['MESHPA = MeSH Pharmacological Actions', 83 | 'VA = Class', 84 | 'PK = Pharmacokinetics', 85 | 'EPC = Established Pharmacological Classes', 86 | 'DISEASE = Indication / Condition / Disease', 87 | 'ATC1-4 = Anatomical Therapeutic Chemical', 88 | 'CHEM = Chemical Name', 89 | 'MOA = Mechanism of Action', 90 | 'PE = Physiological Effect'] 91 | 92 | ``` 93 | 94 |

Drug Indications

95 | 96 | ```python 97 | 98 | helper.indications('bupropion') 99 | 100 | #=> 101 | ['Anorexia', 102 | 'Attention Deficit Disorder with Hyperactivity', 103 | 'Bulimia', 104 | 'Depressive Disorder', 105 | 'Drug Hypersensitivity', 106 | 'Seizures', 107 | 'Substance Withdrawal Syndrome', 108 | 'Tobacco Use Disorder'] 109 | 110 | helper.indications('azithromycin') 111 | 112 | #=> 113 | ['Chlamydia Infections', 114 | 'Endocarditis, Bacterial', 115 | 'Haemophilus Infections', 116 | 'Hypersensitivity', 117 | 'Liver Diseases', 118 | 'Mycobacterium Infections, Nontuberculous', 119 | 'Neisseriaceae Infections', 120 | 'Otitis Media', 121 | 'Pharyngitis', 122 | 'Pneumonia, Bacterial', 123 | 'Pneumonia, Mycoplasma', 124 | 'Respiratory Tract Infections', 125 | 'Sexually Transmitted Diseases, Bacterial', 126 | 'Skin Diseases, Infectious', 127 | 'Staphylococcal Infections', 128 | 'Streptococcal Infections', 129 | 'Tonsillitis', 130 | 'Urethritis'] 131 | 132 | 133 | ``` 134 | 135 |

Drug's Mechanism of Action

136 | 137 | ```python 138 | 139 | helper.mechanism_of_action('marijuana') # 'marijuana' will not be in database 140 | 141 | #=> 142 | [] 143 | 144 | helper.mechanism_of_action('tetrahydrocannabinol') # chemical name will be found though 145 | 146 | #=> 147 | ['Cannabinoid Receptor Agonists'] 148 | 149 | helper.similarly_acting_drugs('fluoxetine') 150 | 151 | #=> 152 | [('Serotonin Uptake Inhibitors', 153 | ['Citalopram', 154 | 'Desvenlafaxine', 155 | 'duloxetine', 156 | 'Escitalopram', 157 | 'Fluoxetine', 158 | 'Fluvoxamine', 159 | 'levomilnacipran', 160 | 'milnacipran', 161 | 'Paroxetine', 162 | 'Sertraline', 163 | 'venlafaxine']), 164 | ('Monoamine Oxidase Inhibitors', 165 | ['Isocarboxazid', 166 | 'Phenelzine', 167 | 'rasagiline', 168 | 'safinamide', 169 | 'Selegiline', 170 | 'Tranylcypromine'])] 171 | 172 | ``` 173 | 174 |

Drug's Physiological Effect

175 | 176 | ```python 177 | 178 | helper.physiological_effect('aripiprazole') 179 | 180 | #=> 181 | ['Decreased Dopamine Activity', 'Decreased Serotonin Activity'] 182 | 183 | helper.drugs_with_similar_physiological_response('ibuprofen') 184 | 185 | #=> 186 | [('Decreased Platelet Activating Factor Production', None), 187 | ('Decreased Prostaglandin Production', ['Aspirin', 'Diclofenac']), 188 | ('Decreased Thromboxane Production', None)] 189 | 190 | helper.drugs_with_similar_physiological_response('aripiprazole') # will yield a false negative 191 | 192 | #=> 193 | [('Decreased Dopamine Activity', None), 194 | ('Decreased Serotonin Activity', None)] 195 | 196 | helper.drugs_with_physiological_effect('Decreased Dopamine Activity') # but this works 197 | 198 | #=> 199 | ('Decreased Dopamine Activity', 200 | ['acetophenazine', 201 | 'aripiprazole', 202 | 'Chlorpromazine', 203 | 'Chlorprothixene', 204 | 'Clozapine', 205 | 'deutetrabenazine', 206 | 'Fluphenazine', 207 | 'Haloperidol', 208 | 'Mesoridazine', 209 | ...]) 210 | 211 | ``` 212 | 213 |

Drug's Pharmacokinetics

214 | 215 | ```python 216 | 217 | helper.pharmacokinetics('ibuprofen') 218 | 219 | #=> 220 | ['Hepatic Metabolism', 'Renal Excretion'] 221 | 222 | helper.drugs_with_similar_pharmacokinetics('ibuprofen') 223 | 224 | #=> 225 | [('Drugs processed via Renal Excretion', 226 | ['Acetaminophen', 227 | 'Albuterol', 228 | 'Alprazolam', 229 | 'Amoxicillin', 230 | 'Aspirin', 231 | 'Atenolol', 232 | ...]), 233 | ('Drugs processed via Hepatic Metabolism', 234 | ['Acetaminophen', 235 | 'Albuterol', 236 | 'Aspirin', 237 | 'atorvastatin', 238 | ...])] 239 | 240 | helper.drugs_with_pharmacokinetics('Hepatic Metabolism') 241 | 242 | #=> 243 | ('Hepatic Metabolism', 244 | ['Acetaminophen', 245 | 'Albuterol', 246 | 'Aspirin', 247 | 'atorvastatin', 248 | 'celecoxib', 249 | 'Codeine', 250 | ...]) 251 | 252 | ``` 253 | 254 |

Therapeutic Class

255 | 256 | ```python 257 | 258 | helper.therapeutic_class('azithromycin') 259 | 260 | #=> 261 | ['Antibiotics', 'Macrolides'] 262 | 263 | helper.therapeutic_class('budesonide') 264 | 265 | #=> 266 | ['Adrenergics in combination with corticosteroids or other drugs, excl. ' 267 | 'anticholinergics', 268 | 'Corticosteroids', 269 | 'Corticosteroids acting locally', 270 | 'Corticosteroids, potent (group III)', 271 | 'Glucocorticoids'] 272 | 273 | ``` 274 | 275 |

Drug Type

276 | 277 | ```python 278 | 279 | helper.drug_type('azithromycin') 280 | 281 | #=> 282 | ['ANTIBACTERIALS,TOPICAL OPHTHALMIC', 'ERYTHROMYCINS/MACROLIDES'] 283 | 284 | helper.drug_type('budesonide') 285 | 286 | #=> 287 | ['ANTI-INFLAMMATORIES,INHALATION', 288 | 'ANTI-INFLAMMATORIES,NASAL', 289 | 'ANTIASTHMA,OTHER', 290 | 'GLUCOCORTICOIDS'] 291 | 292 | ``` 293 | 294 |

Class information of a given drug.

295 | 296 | ```python 297 | 298 | helper.drug_info('ketamine') 299 | 300 | #=> 301 | {'Drug Type': ['GENERAL ANESTHETICS,OTHER'], 302 | 'Indications': ['Aneurysm', 303 | 'Angina Pectoris', 304 | 'Burns', 305 | 'Drug Hypersensitivity', 306 | 'Heart Failure', 307 | 'Hypertension', 308 | 'Intracranial Hypertension', 309 | 'Pain', 310 | 'Psychotic Disorders', 311 | 'Thyrotoxicosis', 312 | 'Unconsciousness'], 313 | 'Mechanism of Action': [], 314 | 'Name': 'Ketamine Hydrochloride', 315 | 'Pharmacokinetics': None, 316 | 'Physiological Effects': ['Blood Pressure Alteration', 317 | 'Decreased Cerebral Cortex Organized Electrical ' 318 | 'Activity', 319 | 'Decreased Midbrain Organized Electrical Activity', 320 | 'Decreased Sensory-Somatic Nervous System ' 321 | 'Organized Electrical Activity', 322 | 'General Anesthesia', 323 | 'Increased Epinephrine Activity', 324 | 'Increased Norepinephrine Activity'], 325 | 'Therapeutic Class': ['Other general anesthetics']} 326 | 327 | ``` 328 | 329 |

Drugs that can induce a reaction or condition.

330 | 331 | ```python 332 | 333 | helper.drug_induces(vomiting') 334 | 335 | #=> 336 | ('Drugs that induce vomiting', 337 | ['Disulfiram', 'ethyl ether', 'Ipecac', 'Nitrous Oxide']) 338 | 339 | helper.drug_induces(seizure disorder') 340 | 341 | #=> 342 | ('Drugs that induce seizure disorder', ['Pentylenetetrazole']) 343 | 344 | ``` 345 | 346 |

Drugs that may prevent a condition or acute reaction.

347 | 348 | ```python 349 | 350 | helper.drugs_that_may('prevent', 'seizure disorder') 351 | 352 | #=> 353 | ('Drugs that may prevent seizure disorder', 354 | ['fosphenytoin', 'Magnesium Sulfate', 'Phenytoin', 'Thiamylal']) 355 | 356 | helper.drugs_that_may('prevent', 'dementia') 357 | 358 | #=> 359 | ('Drugs that may prevent alzheimer disease', ['Vitamin E']) 360 | 361 | ``` 362 | 363 |

Drugs that may treat a condition or acute response.

364 | 365 | ```python 366 | 367 | helper.drugs_that_may('treat', 'seizures') 368 | 369 | #=> 370 | ('Drugs that may treat seizure disorder', 371 | ['Acetazolamide', 372 | 'Amobarbital', 373 | 'Brivaracetam', 374 | 'Carbamazepine', 375 | 'clobazam', 376 | 'Clonazepam', 377 | 'clorazepate', 378 | 'Corticotropin', 379 | 'Diazepam', 380 | 'Ethosuximide', 381 | 'Ethotoin', 382 | 'Etomidate', 383 | 'ezogabine', 384 | 'felbamate', 385 | 'fosphenytoin', 386 | 'gabapentin', 387 | ...]) 388 | 389 | helper.drugs_that_may('treat', 'depressive disorder') 390 | 391 | #=> 392 | ('Drugs that may treat depressive disorder', 393 | ['Alprazolam', 394 | 'Amitriptyline', 395 | 'Amoxapine', 396 | 'brexpiprazole', 397 | 'Bupropion', 398 | 'Buspirone', 399 | 'Citalopram', 400 | 'Clomipramine', 401 | 'Desipramine', 402 | 'Desvenlafaxine', 403 | 'Doxepin', 404 | 'duloxetine', 405 | 'Escitalopram', 406 | 'Fluoxetine', 407 | 'Fluvoxamine', 408 | 'Imipramine', 409 | 'Isocarboxazid', 410 | 'Isoflurane', 411 | 'Kava preparation', 412 | 'levomilnacipran', 413 | 'Lithium', 414 | 'Lorazepam', 415 | 'lurasidone', 416 | 'Maprotiline', 417 | 'Melatonin', 418 | 'Methylphenidate', 419 | 'milnacipran', 420 | ...]) 421 | 422 | ``` 423 | 424 |

Drugs that can diagnose a condition.

425 | 426 | ```python 427 | 428 | helper.drugs_that_may('diagnose', '') 429 | 430 | #=> # yet to find a condition name that yields results from NDFRT database 431 | 432 | ``` 433 | 434 |

Contraindications

435 |

Not as straightforward. Supply 'with' instead of 'DISEASE'.

436 | 437 | ```python 438 | 439 | helper.contraindications('with', 'Drug Hypersensitivity') 440 | 441 | #=> 442 | ('Drug Hypersensitivity contraindications', 443 | ['17-alpha-Hydroxyprogesterone', 444 | '4-Aminobenzoic Acid', 445 | 'abacavir', 446 | 'abciximab', 447 | 'Acarbose', 448 | 'Acebutolol', 449 | 'acemannan', 450 | 'Acetaminophen', 451 | 'Acetazolamide', 452 | 'Acetic Acid', 453 | ...]) 454 | 455 | helper.contraindications('with', 'bulimia') 456 | 457 | #=> 458 | ('bulimia contraindications', ['Bupropion']) 459 | 460 | helper.contraindications('with', 'schizophrenia') 461 | 462 | #=> 463 | ('schizophrenia contraindications', 464 | ['Fenfluramine', 'Ginseng Preparation', 'Tetrahydrocannabinol']) 465 | 466 | ``` 467 | 468 |

Class Subtypes

469 |

Use of this function appears to be limited currently.

470 | 471 | ```python 472 | 473 | helper.subtypes('') 474 | 475 | #=> # Let me know if you find a valid use case that works. 476 | 477 | ``` 478 | 479 |

Spelling Suggestions

480 | 481 | ```python 482 | 483 | helper.class_name_suggestions('amines') 484 | 485 | #=> 486 | ['amines', 487 | 'amides', 488 | 'diamines', 489 | 'azides', 490 | 'amidines', 491 | 'acids', 492 | 'ascites', 493 | 'apnea', 494 | 'anions', 495 | 'amnesia'] 496 | 497 | helper.class_name_suggestions('oxetine', only_drugs=True) # returns only drug names 498 | 499 | #=> 500 | ['fluoxetine', 501 | 'paroxetine', 502 | 'reboxetine', 503 | 'duloxetine', 504 | 'oxypertine', 505 | 'oxerutins', 506 | 'oxetorone'] 507 | 508 | ``` 509 | 510 | 511 |

Statement of Credit

512 | 513 | This product uses publicly available data from the U.S. National Library of Medicine (NLM), National Institutes of Health, Department of Health and Human Services; NLM is not responsible for the product and does not endorse or recommend this or any other product." 514 | -------------------------------------------------------------------------------- /rxclass_api/RxAPIWrapper.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | from pprint import pprint as pp 4 | from functools import reduce 5 | 6 | class RxAPIWrapper(object): 7 | 8 | def __init__(self): 9 | self.base_uri_interactions = 'https://rxnav.nlm.nih.gov/REST/interaction' 10 | self.base_uri_class = 'https://rxnav.nlm.nih.gov/REST/rxclass' 11 | self.base_uri_norm = 'https://rxnav.nlm.nih.gov/REST' 12 | self.drug_class_soaps = [] 13 | 14 | def make_request(api_url_getter): 15 | def wrapper(self, *args): 16 | return requests.get(api_url_getter(self, *args)).json() 17 | return wrapper 18 | 19 | def sanitize(self, opts): 20 | if type(opts) is dict: 21 | return reduce(lambda acc, i: acc + "&{}={}".format(i[0], i[1]), opts.items(), "") 22 | return '' 23 | 24 | @make_request 25 | def get_interaction_uri(self): 26 | return self.base_uri_class + '/interaction.json?rxcui=' + str(341248) 27 | 28 | @make_request 29 | def find_class_by_id(self, drug_class_id): 30 | return self.base_uri_class + "/class/byId.json?classId={}".format(drug_class_id) 31 | 32 | @make_request 33 | def find_class_by_name(self, name): 34 | return self.base_uri_class + "/class/byName.json?className={}".format(name) 35 | 36 | @make_request 37 | def find_class_by_drug_name(self, drug_name, opts=None): 38 | return self.base_uri_class + "/class/byDrugName.json?drugName={}".format(drug_name) + self.sanitize(opts) 39 | 40 | @make_request 41 | def find_similar_classes_by_class(self, class_id, opts=None): 42 | return self.base_uri_class + "/class/similar.json?classId={}".format(class_id) + self.sanitize(opts) 43 | 44 | @make_request 45 | def find_similar_classes_by_drug_list(self, drug_ids, opts=None): 46 | return self.base_uri_class + "/class/similarByRxcuis?rxcuis={}".format(drug_ids) + self.sanitize(opts) 47 | 48 | @make_request 49 | def get_all_classes(self, class_types=None): 50 | return self.base_uri_class + "/allClasses.json" + self.sanitize(class_types) 51 | 52 | @make_request 53 | def get_class_contexts(self, class_id): 54 | return self.base_uri_class + "/classContext.json?classId={}".format(class_id) 55 | 56 | @make_request 57 | def get_class_graph(self, class_id): 58 | return self.base_uri_class + "/classGraph.json?classId={}".format(class_id) 59 | 60 | @make_request 61 | def get_class_members(self, class_id, opts=None): 62 | return self.base_uri_class + "/classMembers.json?classId={}".format(class_id) + self.sanitize(opts) 63 | 64 | @make_request 65 | def get_class_tree(self, class_id): 66 | return self.base_uri_class + "/classTree.json?classId={}".format(class_id) 67 | 68 | @make_request 69 | def get_class_types(self): 70 | return self.base_uri_class + "/classTypes.json" 71 | 72 | @make_request 73 | def get_relationships(self, rela_source): 74 | return self.base_uri_class + "/relas.json?relaSource={}".format(rela_source) 75 | 76 | @make_request 77 | def compare_classes(self, class_id_1, opts=None): 78 | return self.base_uri_class + "/similarInfo.json?" + self.sanitize(opts)[1:] 79 | 80 | @make_request 81 | def get_sources_of_drug_class_relations(self): 82 | return self.base_uri_class + "/relaSources.json" 83 | 84 | @make_request 85 | def get_spelling_suggestions(self, term, type_of_name): 86 | return self.base_uri_class + "/spellingsuggestions.json?term={}&type={}".format(term, type_of_name) 87 | 88 | def save(self): 89 | pp(json.dumps(self.req.json(), "/req_1.json")) 90 | 91 | -------------------------------------------------------------------------------- /rxclass_api/RxClassHelpers.py: -------------------------------------------------------------------------------- 1 | import _pickle as picklerick 2 | import os 3 | from collections import Counter 4 | from functools import reduce 5 | 6 | from .RxAPIWrapper import RxAPIWrapper 7 | 8 | 9 | class RxClassHelpers(object): 10 | 11 | def __init__(self, save_memo=True, filename="rxclass_data"): 12 | self.save_memo = save_memo 13 | self.memo = {} 14 | self.filename = filename 15 | self.api = RxAPIWrapper() 16 | self.drug_class_types = { 17 | 'VA': 'Class', 18 | 'MOA': 'Mechanism of Action', 19 | 'PK': 'Pharmacokinetics', 20 | 'PE': 'Physiological Effect', 21 | 'CHEM': 'Chemical Name', 22 | 'MESHPA': 'MeSH Pharmacological Actions', 23 | 'EPC': 'Established Pharmacological Classes', 24 | 'ATC1-4': 'Anatomical Therapeutic Chemical', 25 | 'DISEASE': 'Indication / Condition / Disease' 26 | } 27 | 28 | def memo(func): 29 | def wrapper(self, drug_name): 30 | if drug_name not in self.memo: 31 | self.get_class_data_of_drug(drug_name) 32 | return func(self, drug_name) 33 | return wrapper 34 | 35 | 36 | def get_class_data_of_drug(self, drug_name): 37 | ret = self.api.find_class_by_drug_name(drug_name) 38 | if 'rxclassDrugInfoList' not in ret: 39 | error = "{} not found in database".format(drug_name) 40 | self.memo[drug_name] = [error] 41 | return error 42 | classes = { 43 | (source['rxclassMinConceptItem']['className'], 44 | source['rxclassMinConceptItem']['classType'], 45 | source['rxclassMinConceptItem']['classId']) 46 | for source in ret['rxclassDrugInfoList']['rxclassDrugInfo'] 47 | } 48 | arranged_classes = {} 49 | for tup in classes: 50 | if tup[1] not in arranged_classes: 51 | arranged_classes[tup[1]] = [(tup[2], tup[0])] 52 | else: 53 | arranged_classes[tup[1]].append((tup[2], tup[0])) 54 | self.memo[drug_name] = arranged_classes 55 | return arranged_classes 56 | 57 | 58 | @memo 59 | def similarly_acting_drugs(self, drug_name): 60 | if 'MOA' not in self.memo[drug_name]: 61 | return "{} has no recorded mechanism of action.".format(drug_name) 62 | pairs = self.memo[drug_name]['MOA'] 63 | moa_data = [] 64 | def get_similar(id, name): 65 | opts = { 66 | 'relaSource': 'DAILYMED', 67 | 'rela': "has_{}".format('MOA') 68 | } 69 | ret = self.api.get_class_members(id, opts) 70 | if 'drugMemberGroup' not in ret: 71 | return name, None 72 | return name, [member['minConcept']['name'] for member in ret['drugMemberGroup']['drugMember']] 73 | for moa_id, moa_name in pairs: 74 | moa_data.append(get_similar(moa_id, moa_name)) 75 | return moa_data 76 | 77 | def contraindications(self, rela, class_name): 78 | ret = self.get_class_by_name(class_name) 79 | if 'classId' not in ret: 80 | return "{} not found in database.".format(class_name) 81 | class_type_id = ret['classId'] 82 | opts = { 83 | 'relaSource': 'NDFRT', 84 | 'rela': "CI_{}".format(rela) 85 | } 86 | ret = self.api.get_class_members(class_type_id, opts) 87 | title = "{} contraindications".format(class_name) 88 | if 'drugMemberGroup' not in ret: 89 | return title, None 90 | drug_names = [member['minConcept']['name'] for member in ret['drugMemberGroup']['drugMember']] 91 | return title, drug_names 92 | 93 | def drug_induces(self, disease): 94 | ret = self.get_class_by_name(disease) 95 | if 'classId' not in ret: 96 | return "{} not found in database.".format(disease) 97 | disease_id = ret['classId'] 98 | opts = { 99 | 'relaSource': 'NDFRT', 100 | 'rela': 'induces' 101 | } 102 | ret = self.api.get_class_members(disease_id, opts) 103 | title = "Drugs that induce {}".format(disease) 104 | if 'drugMemberGroup' not in ret: 105 | return title, None 106 | drug_names = [member['minConcept']['name'] for member in ret['drugMemberGroup']['drugMember']] 107 | return title, drug_names 108 | 109 | def drugs_that_may(self, action, disease): 110 | ret = self.get_class_by_name(disease) 111 | if 'classId' not in ret: 112 | return "{} not found in database.".format(disease) 113 | disease_id = ret['classId'] 114 | opts = { 115 | 'relaSource': 'NDFRT', 116 | 'rela': "may_{}".format(action)# action items allowed: prevent, diagnose, treat 117 | } 118 | ret = self.api.get_class_members(disease_id, opts) 119 | title = "Drugs that may {} {}".format(action, disease) 120 | if 'drugMemberGroup' not in ret: 121 | return title, None 122 | drug_names = [member['minConcept']['name'] for member in ret['drugMemberGroup']['drugMember']] 123 | return title, drug_names 124 | 125 | @memo 126 | def drugs_with_similar_physiological_response(self, drug_name): 127 | if not 'PE' in self.memo[drug_name]: 128 | return "{} does not have a recorded physiological response or was not found in database.".format(drug_name) 129 | pairs = self.memo[drug_name]['PE'] 130 | pe_data = [] 131 | def get_similar(id, name): 132 | opts = { 133 | 'relaSource': 'DAILYMED', 134 | 'rela': 'has_PE' 135 | } 136 | ret = self.api.get_class_members(id, opts) 137 | if 'drugMemberGroup' not in ret: 138 | return name, None 139 | return name, [member['minConcept']['name'] for member in ret['drugMemberGroup']['drugMember']] 140 | for pe_id, pe_name in pairs: 141 | pe_data.append(get_similar(pe_id, pe_name)) 142 | return pe_data 143 | 144 | def drugs_with_physiological_effect(self, effect): 145 | ret = self.get_class_by_name(effect) 146 | if 'classId' not in ret: 147 | return "{} not found in database.".format(effect) 148 | effect_id = ret['classId'] 149 | opts = { 150 | 'relaSource': 'NDFRT', 151 | 'rela': 'has_PE' 152 | } 153 | ret = self.api.get_class_members(effect_id, opts) 154 | if 'drugMemberGroup' not in ret: 155 | return effect, None 156 | drug_names = [member['minConcept']['name'] for member in ret['drugMemberGroup']['drugMember']] 157 | return effect, drug_names 158 | 159 | @memo 160 | def drugs_with_similar_pharmacokinetics(self, drug_name): 161 | pairs = self.memo[drug_name]['PK'] 162 | pe_data = [] 163 | def get_similar(id, name): 164 | opts = { 165 | 'relaSource': 'NDFRT', 166 | 'rela': 'has_PK' 167 | } 168 | ret = self.api.get_class_members(id, opts) 169 | if 'drugMemberGroup' not in ret: 170 | return name, None 171 | drug_names = [member['minConcept']['name'] for member in ret['drugMemberGroup']['drugMember']] 172 | return name, drug_names 173 | for pk_id, pk_name in pairs: 174 | pe_data.append(get_similar(pk_id, pk_name)) 175 | return pe_data 176 | 177 | 178 | def drugs_with_pharmacokinetics(self, pe_name): 179 | ret = self.get_class_by_name(pe_name) 180 | if 'classId' not in ret: 181 | return "{} not found in database.".format(pe_name) 182 | pk_id = ret['classId'] 183 | opts = { 184 | 'relaSource': 'NDFRT', 185 | 'rela': 'has_PK' 186 | } 187 | ret = self.api.get_class_members(pk_id, opts) 188 | if 'drugMemberGroup' not in ret: 189 | return pe_name, None 190 | drug_names = [member['minConcept']['name'] for member in ret['drugMemberGroup']['drugMember']] 191 | return pe_name, drug_names 192 | 193 | 194 | def get_class_by_name(self, class_name): 195 | ret = self.api.find_class_by_name(class_name) 196 | if 'rxclassMinConceptList' not in ret: 197 | return ret 198 | return ret['rxclassMinConceptList']['rxclassMinConcept'][0] 199 | 200 | def get_class_by_id(self, class_id): 201 | ret = self.api.find_class_by_id(class_id) 202 | if 'rxclassMinConceptList' not in ret: 203 | return ret 204 | return ret['rxclassMinConceptList']['rxclassMinConcept'][0] 205 | 206 | def similar_classes(self, class_name, limit=10): 207 | ret = self.get_class_by_name(class_name) 208 | if 'classId' not in ret: 209 | return "{} not found in database.".format(class_name) 210 | opts = { 211 | 'relaSource': 'ATC', 212 | #'rela': 'MOA', 213 | 'scoreType': 2, 214 | 'top': limit, 215 | 'equivalenceThreshold': 0.3, 216 | 'inclusionThreshold': 0.3 217 | } 218 | ret = self.api.find_similar_classes_by_class(ret['classId'], opts) 219 | 220 | 221 | 222 | @memo 223 | def mechanism_of_action(self, drug_name): 224 | moa = set() 225 | meshpa = set() 226 | if 'MOA' in self.memo[drug_name]: 227 | moa = set([tup[1] for tup in self.memo[drug_name]['MOA']]) 228 | if 'MESHPA' in self.memo[drug_name]: 229 | meshpa = set([tup[1] for tup in self.memo[drug_name]['MESHPA']]) 230 | 231 | return sorted(moa & meshpa) 232 | 233 | @memo 234 | def drug_type(self, drug_name): 235 | if 'VA' not in self.memo[drug_name]: 236 | return 237 | return sorted(set([tup[1] for tup in self.memo[drug_name]['VA']])) 238 | 239 | @memo 240 | def indications(self, drug_name): 241 | if 'DISEASE' not in self.memo[drug_name]: 242 | return 243 | return sorted(set([tup[1] for tup in self.memo[drug_name]['DISEASE']])) 244 | 245 | @memo 246 | def physiological_effects(self, drug_name): 247 | if 'PE' not in self.memo[drug_name]: 248 | return 249 | return sorted(set([tup[1] for tup in self.memo[drug_name]['PE']])) 250 | 251 | @memo 252 | def pharmacokinetics(self, drug_name): 253 | if 'PK' not in self.memo[drug_name]: 254 | return 255 | return sorted(set([tup[1] for tup in self.memo[drug_name]['PK']])) 256 | 257 | @memo 258 | def therapeutic_class(self, drug_name): 259 | if 'ATC1-4' not in self.memo[drug_name]: 260 | return 261 | return sorted(set([tup[1] for tup in self.memo[drug_name]['ATC1-4']])) 262 | 263 | @memo 264 | def pharmacological_classes(self, drug_name): 265 | if 'EPC' not in self.memo[drug_name]: 266 | return 267 | return sorted(set([tup[1] for tup in self.memo[drug_name]['EPC']])) 268 | 269 | @memo 270 | def chemical_name_of_brand(self, brand_name): 271 | if 'CHEM' not in self.memo[brand_name]: 272 | return 273 | return Counter([tup[1] for tup in self.memo[brand_name]['CHEM']]).most_common(1)[0][0] 274 | 275 | 276 | def subtypes(self, class_name): 277 | ret = self.get_class_by_name(class_name) 278 | if 'classId' not in ret: 279 | return "{} not found in database".format(class_name) 280 | class_id = ret['classId'] 281 | ret = self.api.get_class_tree(class_id) 282 | title = "Subtypes of {}".format(class_name) 283 | if 'rxClassTree' not in ret: 284 | return title, None 285 | subtypes = [sub['minConcept'] for sub in ret['rxClassTree']['rxClass']] 286 | return title, subtypes 287 | 288 | def class_name_suggestions(self, class_name, only_drugs=False, class_type='CLASS'): 289 | if only_drugs: 290 | class_type = 'DRUGS' 291 | ret = self.api.get_spelling_suggestions(class_name, class_type) 292 | if 'suggestionList' not in ret: 293 | return "Items similarly named to {} were not found.".format(class_name) 294 | return ret['suggestionList']['suggestion'] 295 | 296 | 297 | def list_class_types(self): 298 | return reduce(lambda acc, ct: acc + ["{} = {}".format(ct[0], ct[1])], self.drug_class_types.items(), []) 299 | 300 | def drug_info(self, drug_name): 301 | return { 302 | 'Name': self.chemical_name_of_brand(drug_name), 303 | 'Drug Type': self.drug_type(drug_name), 304 | 'Therapeutic Class': self.therapeutic_class(drug_name), 305 | 'Indications': self.indications(drug_name), 306 | 'Mechanism of Action': self.mechanism_of_action(drug_name), 307 | 'Physiological Effects': self.physiological_effects(drug_name), 308 | 'Pharmacokinetics': self.pharmacokinetics(drug_name), 309 | } 310 | 311 | def save(self): 312 | with open("{}.p".format(self.filename), 'wb') as f: 313 | picklerick.dump(self.memo, f) 314 | 315 | def wipe(self): 316 | open("{}.p".format(self.filename), 'wb').close() 317 | 318 | def load(self): 319 | if not os.path.exists("{}.p".format(self.filename)): 320 | with open("{}.p".format(self.filename), 'wb') as f: 321 | picklerick.dump({}, f) 322 | with open("{}.p".format(self.filename), 'rb') as f: 323 | try: 324 | data = picklerick.load(f) 325 | except EOFError: 326 | data = {} 327 | return data 328 | 329 | def __enter__(self): 330 | self.memo = self.load() 331 | return self 332 | 333 | def __exit__(self, type, value, traceback): 334 | if self.save_memo: 335 | self.save() 336 | -------------------------------------------------------------------------------- /rxclass_api/__init__.py: -------------------------------------------------------------------------------- 1 | from .RxAPIWrapper import RxAPIWrapper 2 | from .RxClassHelpers import RxClassHelpers -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='rxclass', 5 | description='Python wrapper for RxClass API', 6 | url='https://github.com/Ianphorsman/RxClassAPIWrapper', 7 | author='Ian Horsman', 8 | author_email='ianphorsman@gmail.com', 9 | license='MIT', 10 | version='0.1.2', 11 | packages=['rxclass_api'] 12 | ) --------------------------------------------------------------------------------