Repository: cancervariants/therapy-normalization (language: python)

Issue:
Implement ChemIDPlus source
https://www.nlm.nih.gov/databases/download/chemidplus.html

https://chem.nlm.nih.gov/chemidsearch/api

Outstanding questions:

* Some records have multiple "NameOfSubstance" attributes - could possibly resolve by "Source" tag? (currently grabbing the first item listed -- which appears to always be NLM)
* Label vs trade names - "NameOfSubstance" vs "SystematicName"?
Issue 72



Discussion / hints:

Fix patch:
diff --git a/Pipfile b/Pipfile
index a7fe18e0..b70fd05b 100644
--- a/Pipfile
+++ b/Pipfile
@@ -30,6 +30,9 @@ uvloop = "*"
 websockets = "*"
 httptools = "*"
 typing-extensions = "*"
+ipykernel = "*"
+bs4 = "*"
+pyyaml = "*"
 
 [requires]
 python_version = "3.8"
diff --git a/Pipfile.lock b/Pipfile.lock
index 244d71bc..5b069abd 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "2891b3aea605231d2c8e9e31fd984b7d9c7aaf9f38870b5724b18c1eda7f12c9"
+            "sha256": "77b2ac70c012ad1bf7cf4cdedc4fd585d6a9643f0c83b51e153dd815ef0cc291"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -16,28 +16,58 @@
         ]
     },
     "default": {
+        "appnope": {
+            "hashes": [
+                "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442",
+                "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"
+            ],
+            "markers": "sys_platform == 'darwin' and platform_system == 'Darwin'",
+            "version": "==0.1.2"
+        },
+        "backcall": {
+            "hashes": [
+                "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e",
+                "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"
+            ],
+            "version": "==0.2.0"
+        },
+        "beautifulsoup4": {
+            "hashes": [
+                "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35",
+                "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25",
+                "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"
+            ],
+            "version": "==4.9.3"
+        },
         "bleach": {
             "hashes": [
-                "sha256:52b5919b81842b1854196eaae5ca29679a2f2e378905c346d3ca8227c2c66080",
-                "sha256:9f8ccbeb6183c6e6cddea37592dfb0167485c1e3b13b3363bc325aa8bda3adbd"
+                "sha256:a690ccc41a10d806a7c0a9130767750925e4863e332f7e4ea93da1bc12a24300",
+                "sha256:ce6270dd0ae56cd810495b8d994551ae16b41f2b4043cf50064f298985afdb3c"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
-            "version": "==3.2.1"
+            "version": "==3.2.2"
         },
         "boto3": {
             "hashes": [
-                "sha256:0bb2c3159b9f5e0df50430bf06a155bd7f27f480825b6374dde807d42360a668",
-                "sha256:a49b3ab4bfa2f6394ba60165cfc468410797dd410f32eed47e22f61451ee986e"
+                "sha256:2fd3c2f42006988dc8ddae43c988aea481d11e2af7ab1deb83b293640357986c",
+                "sha256:4a499cc2f53dd557a88c6db6a552748a2abd83ffeda70ceb71dc8db39a027314"
             ],
             "index": "pypi",
-            "version": "==1.16.43"
+            "version": "==1.16.57"
         },
         "botocore": {
             "hashes": [
-                "sha256:7398c900dbd4e3d61647269215396ea3e8082f494f3e7b65d9b6aca049c1d463",
-                "sha256:795a67338cadb0c3a45014a6c81659da6af623a4e973812f87a6f9d9fb7712e9"
+                "sha256:c756d65ffa989c5c0e92178175e41abf7b18ad19b2fe2e82e192f085e264e03a",
+                "sha256:cf7d108a4d67a0fe670379111927b5d9e0ff1160146c81c326bb9e54c2b8cb19"
+            ],
+            "version": "==1.19.57"
+        },
+        "bs4": {
+            "hashes": [
+                "sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"
             ],
-            "version": "==1.19.43"
+            "index": "pypi",
+            "version": "==0.0.1"
         },
         "certifi": {
             "hashes": [
@@ -111,6 +141,13 @@
             "index": "pypi",
             "version": "==0.29.21"
         },
+        "decorator": {
+            "hashes": [
+                "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760",
+                "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"
+            ],
+            "version": "==4.4.2"
+        },
         "docutils": {
             "hashes": [
                 "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
@@ -129,10 +166,11 @@
         },
         "h11": {
             "hashes": [
-                "sha256:3c6c61d69c6f13d41f1b80ab0322f1872702a3ba26e12aa864c928f6a43fbaab",
-                "sha256:ab6c335e1b6ef34b205d5ca3e228c9299cc7218b049819ec84a388c2525e5d87"
+                "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6",
+                "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"
             ],
-            "version": "==0.11.0"
+            "markers": "python_version >= '3.6'",
+            "version": "==0.12.0"
         },
         "httptools": {
             "hashes": [
@@ -160,6 +198,29 @@
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==2.10"
         },
+        "ipykernel": {
+            "hashes": [
+                "sha256:4ed205700001a83b5832d4821c46a5733f1bf4b1c55744314ae3c756be6b6095",
+                "sha256:697103d218e9a8828025af7986e033c89e0b36e2b6eb84a5bda4739b9a27f3cb"
+            ],
+            "index": "pypi",
+            "version": "==5.4.3"
+        },
+        "ipython": {
+            "hashes": [
+                "sha256:c987e8178ced651532b3b1ff9965925bfd445c279239697052561a9ab806d28f",
+                "sha256:cbb2ef3d5961d44e6a963b9817d4ea4e1fa2eb589c371a470fed14d8d40cbd6a"
+            ],
+            "markers": "python_version >= '3.7'",
+            "version": "==7.19.0"
+        },
+        "ipython-genutils": {
+            "hashes": [
+                "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8",
+                "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"
+            ],
+            "version": "==0.2.0"
+        },
         "isodate": {
             "hashes": [
                 "sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8",
@@ -167,21 +228,45 @@
             ],
             "version": "==0.6.0"
         },
+        "jedi": {
+            "hashes": [
+                "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93",
+                "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==0.18.0"
+        },
         "jmespath": {
             "hashes": [
                 "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9",
                 "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"
             ],
-            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
+            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==0.10.0"
         },
+        "jupyter-client": {
+            "hashes": [
+                "sha256:5eaaa41df449167ebba5e1cf6ca9b31f7fd4f71625069836e2e4fee07fe3cb13",
+                "sha256:649ca3aca1e28f27d73ef15868a7c7f10d6e70f761514582accec3ca6bb13085"
+            ],
+            "markers": "python_version >= '3.5'",
+            "version": "==6.1.11"
+        },
+        "jupyter-core": {
+            "hashes": [
+                "sha256:0a451c9b295e4db772bdd8d06f2f1eb31caeec0e81fbb77ba37d4a3024e3b315",
+                "sha256:aa1f9496ab3abe72da4efe0daab0cb2233997914581f9a071e07498c6add8ed3"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==4.7.0"
+        },
         "keyring": {
             "hashes": [
-                "sha256:4c41ce4f6d1ee91d589a346699ef5a94ba3429603ac8f700cc0097644cdd6748",
-                "sha256:a144f7e1044c897c3976202af868cb0ac860f4d433d5d0f8e750fa1a2f0f0b50"
+                "sha256:1746d3ac913d449a090caf11e9e4af00e26c3f7f7e81027872192b2398b98675",
+                "sha256:4be9cbaaaf83e61d6399f733d113ede7d1c73bc75cb6aeb64eee0f6ac39b30ea"
             ],
             "markers": "python_version >= '3.6'",
-            "version": "==21.7.0"
+            "version": "==21.8.0"
         },
         "lxml": {
             "hashes": [
@@ -241,12 +326,50 @@
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==20.8"
         },
+        "parso": {
+            "hashes": [
+                "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410",
+                "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==0.8.1"
+        },
+        "pexpect": {
+            "hashes": [
+                "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937",
+                "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"
+            ],
+            "markers": "sys_platform != 'win32'",
+            "version": "==4.8.0"
+        },
+        "pickleshare": {
+            "hashes": [
+                "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca",
+                "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"
+            ],
+            "version": "==0.7.5"
+        },
         "pkginfo": {
             "hashes": [
-                "sha256:a6a4ac943b496745cec21f14f021bbd869d5e9b4f6ec06918cffea5a2f4b9193",
-                "sha256:ce14d7296c673dc4c61c759a0b6c14bae34e34eb819c0017bb6ca5b7292c56e9"
+                "sha256:029a70cb45c6171c329dfc890cde0879f8c52d6f3922794796e06f577bb03db4",
+                "sha256:9fdbea6495622e022cc72c2e5e1b735218e4ffb2a2a69cde2694a6c1f16afb75"
+            ],
+            "version": "==1.7.0"
+        },
+        "prompt-toolkit": {
+            "hashes": [
+                "sha256:0bdd2585e5afd00c5f91dd28eb2090ea67e94c385878921939bb4ccfa3904723",
+                "sha256:dc83e6368b0edd9ceabe17a055f2e22f6ed95b9aa39dbd59d0b4f3585bdfe9ed"
+            ],
+            "markers": "python_full_version >= '3.6.1'",
+            "version": "==3.0.11"
+        },
+        "ptyprocess": {
+            "hashes": [
+                "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35",
+                "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"
             ],
-            "version": "==1.6.1"
+            "version": "==0.7.0"
         },
         "pydantic": {
             "hashes": [
@@ -278,18 +401,18 @@
         },
         "pygments": {
             "hashes": [
-                "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716",
-                "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"
+                "sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435",
+                "sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"
             ],
             "markers": "python_version >= '3.5'",
-            "version": "==2.7.3"
+            "version": "==2.7.4"
         },
         "pyparsing": {
             "hashes": [
                 "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
                 "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
             ],
-            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
+            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==2.4.7"
         },
         "python-dateutil": {
@@ -297,9 +420,70 @@
                 "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
                 "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==2.8.1"
         },
+        "pyyaml": {
+            "hashes": [
+                "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf",
+                "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696",
+                "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393",
+                "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77",
+                "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922",
+                "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5",
+                "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8",
+                "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10",
+                "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc",
+                "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
+                "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
+                "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
+                "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
+                "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
+                "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
+                "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
+                "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
+                "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
+                "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
+                "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
+                "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"
+            ],
+            "index": "pypi",
+            "version": "==5.4.1"
+        },
+        "pyzmq": {
+            "hashes": [
+                "sha256:01715453ce14d4b804f87969461d21fff47df9bebde3c283c1ad872207717abc",
+                "sha256:083dd4c1e9bc058acabab5d95e25180cec224ca9d372b088bf204b0822b278a9",
+                "sha256:20c53aff015001cb705db0928850fa74ea4280a935d4e726743e4cb13206b0f2",
+                "sha256:2199156013875ff4f872daa86214fe34658e4017b5cd8c4a2c4d6d9b59d1a2eb",
+                "sha256:405e754799480d960df7d8249192c4e46288d41d08aaaa45f339269bc09f3c0a",
+                "sha256:520a80148c26cfbfb76fd169c089e7a899071dd5cd7553269e4da149382b9b88",
+                "sha256:5adc4e3015c647e413bdcf3cac803ffdb8566b938f83e5234ab9c2c14fe3ea3a",
+                "sha256:69e5c1061a2e99ac2647db271a41cb5c95ff62dd5090a948b1fbca905c5cba81",
+                "sha256:76e1b4dff2be48ed98ec34dd10ad97316e69cb5ff37754f84abc9fb4bbc949bc",
+                "sha256:77371c7a39d2f1b71444128b9377be8b0588c3fbf7f56db970c5d4b7af8ed9fd",
+                "sha256:7ca684fdb433577c30243357813eef81973d5dbbc3c6c1568e6c21ec1dcedda3",
+                "sha256:7ea55c672840ee8fd5884134c0697845d28f5b053713fc682b5d5fc73d747853",
+                "sha256:8f17f71430c18666c0f6c81185ef494f59231d01b1f77f67debfe628d50479c6",
+                "sha256:9026acf8bf0852c8360c574d04d22d7a213dafaf04ab9c4d43c7430eda272cdd",
+                "sha256:923ec92c7b82d63bab4193aee23fd4a2b1636369494d55883fbda10fef1075a3",
+                "sha256:930e33d92e7d991a1c194790c7fc7f3099f7ec1447e853b4218cba914bee3b7b",
+                "sha256:a2b9e25ea0f81e920de3bff65a5bd9056acd81f8cb439546d00d77f386cba251",
+                "sha256:a82f6f41523db5408925b82bb150ecbc625c2eeccf31d38fa1a0e395e11dd5e2",
+                "sha256:b1fb293a5562a4870f20bb859a50bd59c14fdb1fc13353e25267facaf68f6eb0",
+                "sha256:b2a5d5fd2857e5006a5fd9067f5aa7aff0cd4f994180681b13a6bd724a5ce289",
+                "sha256:c12fba29f0b956390aed37d463fbea215d7592c08241fb20a2c165ef64c95019",
+                "sha256:c3a630dd7716e8e127d43b22598e256a2d11a847b8cc3310350528960037fa06",
+                "sha256:de00a0fe9735efa06b96af56c8e7baa67c0972ec510e18c98efbb593c73cd886",
+                "sha256:e51ea97103791597e4deca13992c3544224c7eed89dc575d9a85972b16f01b59",
+                "sha256:e98d9b9efb22ece82b06046ba0c00cce157cbfd852cbd9a385b338f295cf38e6",
+                "sha256:f1e357e234b435441b9366f6958623abe74fbbb1bd8e3bc679f09b5126785785",
+                "sha256:f321b1e2ea990e9e760c1894234ee426e150995691c05b840a0d9743f5f202e1",
+                "sha256:fe0186c70fd3205b31daaa024409b8887af9b0344f47bc4d5ed03f08f64b9552"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==21.0.1"
+        },
         "rdflib": {
             "hashes": [
                 "sha256:78149dd49d385efec3b3adfbd61c87afaf1281c30d3fcaf1b323b34f603fb155",
@@ -339,19 +523,27 @@
         },
         "s3transfer": {
             "hashes": [
-                "sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13",
-                "sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db"
+                "sha256:1e28620e5b444652ed752cf87c7e0cb15b0e578972568c6609f0f18212f259ed",
+                "sha256:7fdddb4f22275cf1d32129e21f056337fd2a80b6ccef1664528145b72c49e6d2"
             ],
-            "version": "==0.3.3"
+            "version": "==0.3.4"
         },
         "six": {
             "hashes": [
                 "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
                 "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==1.15.0"
         },
+        "soupsieve": {
+            "hashes": [
+                "sha256:4bb21a6ee4707bf43b61230e80740e71bfe56e55d1f1f50924b087bb2975c851",
+                "sha256:6dc52924dc0bc710a5d16794e6b3480b2c7c08b07729505feab2b2c16661ff6e"
+            ],
+            "markers": "python_version >= '3.0'",
+            "version": "==2.1"
+        },
         "starlette": {
             "hashes": [
                 "sha256:bd2ffe5e37fb75d014728511f8e68ebf2c80b0fa3d04ca1479f4dc752ae31ac9",
@@ -360,13 +552,68 @@
             "markers": "python_version >= '3.6'",
             "version": "==0.13.6"
         },
+        "tornado": {
+            "hashes": [
+                "sha256:0a00ff4561e2929a2c37ce706cb8233b7907e0cdc22eab98888aca5dd3775feb",
+                "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c",
+                "sha256:1e8225a1070cd8eec59a996c43229fe8f95689cb16e552d130b9793cb570a288",
+                "sha256:20241b3cb4f425e971cb0a8e4ffc9b0a861530ae3c52f2b0434e6c1b57e9fd95",
+                "sha256:25ad220258349a12ae87ede08a7b04aca51237721f63b1808d39bdb4b2164558",
+                "sha256:33892118b165401f291070100d6d09359ca74addda679b60390b09f8ef325ffe",
+                "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791",
+                "sha256:3447475585bae2e77ecb832fc0300c3695516a47d46cefa0528181a34c5b9d3d",
+                "sha256:34ca2dac9e4d7afb0bed4677512e36a52f09caa6fded70b4e3e1c89dbd92c326",
+                "sha256:3e63498f680547ed24d2c71e6497f24bca791aca2fe116dbc2bd0ac7f191691b",
+                "sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4",
+                "sha256:6196a5c39286cc37c024cd78834fb9345e464525d8991c21e908cc046d1cc02c",
+                "sha256:61b32d06ae8a036a6607805e6720ef00a3c98207038444ba7fd3d169cd998910",
+                "sha256:6286efab1ed6e74b7028327365cf7346b1d777d63ab30e21a0f4d5b275fc17d5",
+                "sha256:65d98939f1a2e74b58839f8c4dab3b6b3c1ce84972ae712be02845e65391ac7c",
+                "sha256:66324e4e1beede9ac79e60f88de548da58b1f8ab4b2f1354d8375774f997e6c0",
+                "sha256:6c77c9937962577a6a76917845d06af6ab9197702a42e1346d8ae2e76b5e3675",
+                "sha256:70dec29e8ac485dbf57481baee40781c63e381bebea080991893cd297742b8fd",
+                "sha256:7250a3fa399f08ec9cb3f7b1b987955d17e044f1ade821b32e5f435130250d7f",
+                "sha256:748290bf9112b581c525e6e6d3820621ff020ed95af6f17fedef416b27ed564c",
+                "sha256:7da13da6f985aab7f6f28debab00c67ff9cbacd588e8477034c0652ac141feea",
+                "sha256:8f959b26f2634a091bb42241c3ed8d3cedb506e7c27b8dd5c7b9f745318ddbb6",
+                "sha256:9de9e5188a782be6b1ce866e8a51bc76a0fbaa0e16613823fc38e4fc2556ad05",
+                "sha256:a48900ecea1cbb71b8c71c620dee15b62f85f7c14189bdeee54966fbd9a0c5bd",
+                "sha256:b87936fd2c317b6ee08a5741ea06b9d11a6074ef4cc42e031bc6403f82a32575",
+                "sha256:c77da1263aa361938476f04c4b6c8916001b90b2c2fdd92d8d535e1af48fba5a",
+                "sha256:cb5ec8eead331e3bb4ce8066cf06d2dfef1bfb1b2a73082dfe8a161301b76e37",
+                "sha256:cc0ee35043162abbf717b7df924597ade8e5395e7b66d18270116f8745ceb795",
+                "sha256:d14d30e7f46a0476efb0deb5b61343b1526f73ebb5ed84f23dc794bdb88f9d9f",
+                "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32",
+                "sha256:d3d20ea5782ba63ed13bc2b8c291a053c8d807a8fa927d941bd718468f7b950c",
+                "sha256:d3f7594930c423fd9f5d1a76bee85a2c36fd8b4b16921cae7e965f22575e9c01",
+                "sha256:dcef026f608f678c118779cd6591c8af6e9b4155c44e0d1bc0c87c036fb8c8c4",
+                "sha256:e0791ac58d91ac58f694d8d2957884df8e4e2f6687cdf367ef7eb7497f79eaa2",
+                "sha256:e385b637ac3acaae8022e7e47dfa7b83d3620e432e3ecb9a3f7f58f150e50921",
+                "sha256:e519d64089b0876c7b467274468709dadf11e41d65f63bba207e04217f47c085",
+                "sha256:e7229e60ac41a1202444497ddde70a48d33909e484f96eb0da9baf8dc68541df",
+                "sha256:ed3ad863b1b40cd1d4bd21e7498329ccaece75db5a5bf58cd3c9f130843e7102",
+                "sha256:f0ba29bafd8e7e22920567ce0d232c26d4d47c8b5cf4ed7b562b5db39fa199c5",
+                "sha256:fa2ba70284fa42c2a5ecb35e322e68823288a4251f9ba9cc77be04ae15eada68",
+                "sha256:fba85b6cd9c39be262fcd23865652920832b61583de2a2ca907dbd8e8a8c81e5"
+            ],
+            "markers": "python_version >= '3.5'",
+            "version": "==6.1"
+        },
         "tqdm": {
             "hashes": [
-                "sha256:38b658a3e4ecf9b4f6f8ff75ca16221ae3378b2e175d846b6b33ea3a20852cf5",
-                "sha256:d4f413aecb61c9779888c64ddf0c62910ad56dcbe857d8922bb505d4dbff0df1"
+                "sha256:4621f6823bab46a9cc33d48105753ccbea671b68bab2c50a9f0be23d4065cb5a",
+                "sha256:fe3d08dd00a526850568d542ff9de9bbc2a09a791da3c334f3213d8d0bbbca65"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==4.54.1"
+            "version": "==4.56.0"
+        },
+        "traitlets": {
+            "hashes": [
+                "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396",
+                "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"
+            ],
+            "markers": "python_version >= '3.7'",
+            "version": "==5.0.5"
         },
         "twine": {
             "hashes": [
@@ -395,11 +642,11 @@
         },
         "uvicorn": {
             "hashes": [
-                "sha256:6707fa7f4dbd86fd6982a2d4ecdaad2704e4514d23a1e4278104311288b04691",
-                "sha256:d19ca083bebd212843e01f689900e5c637a292c63bb336c7f0735a99300a5f38"
+                "sha256:1079c50a06f6338095b4f203e7861dbff318dde5f22f3a324fc6e94c7654164c",
+                "sha256:ef1e0bb5f7941c6fe324e06443ddac0331e1632a776175f87891c7bd02694355"
             ],
             "index": "pypi",
-            "version": "==0.13.2"
+            "version": "==0.13.3"
         },
         "uvloop": {
             "hashes": [
@@ -416,6 +663,13 @@
             "index": "pypi",
             "version": "==0.14.0"
         },
+        "wcwidth": {
+            "hashes": [
+                "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784",
+                "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"
+            ],
+            "version": "==0.2.5"
+        },
         "webencodings": {
             "hashes": [
                 "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
@@ -538,11 +792,11 @@
         },
         "bleach": {
             "hashes": [
-                "sha256:52b5919b81842b1854196eaae5ca29679a2f2e378905c346d3ca8227c2c66080",
-                "sha256:9f8ccbeb6183c6e6cddea37592dfb0167485c1e3b13b3363bc325aa8bda3adbd"
+                "sha256:a690ccc41a10d806a7c0a9130767750925e4863e332f7e4ea93da1bc12a24300",
+                "sha256:ce6270dd0ae56cd810495b8d994551ae16b41f2b4043cf50064f298985afdb3c"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
-            "version": "==3.2.1"
+            "version": "==3.2.2"
         },
         "certifi": {
             "hashes": [
@@ -681,11 +935,11 @@
         },
         "coveralls": {
             "hashes": [
-                "sha256:2301a19500b06649d2ec4f2858f9c69638d7699a4c63027c5d53daba666147cc",
-                "sha256:b990ba1f7bc4288e63340be0433698c1efe8217f78c689d254c2540af3d38617"
+                "sha256:5399c0565ab822a70a477f7031f6c88a9dd196b3de2877b3facb43b51bd13434",
+                "sha256:f8384968c57dee4b7133ae701ecdad88e85e30597d496dcba0d7fbb470dca41f"
             ],
             "index": "pypi",
-            "version": "==2.2.0"
+            "version": "==3.0.0"
         },
         "decorator": {
             "hashes": [
@@ -755,11 +1009,11 @@
         },
         "identify": {
             "hashes": [
-                "sha256:943cd299ac7f5715fcb3f684e2fc1594c1e0f22a90d15398e5888143bd4144b5",
-                "sha256:cc86e6a9a390879dcc2976cef169dd9cc48843ed70b7380f321d1b118163c60e"
+                "sha256:70b638cf4743f33042bebb3b51e25261a0a10e80f978739f17e7fd4837664a66",
+                "sha256:9dfb63a2e871b807e3ba62f029813552a24b5289504f5b071dea9b041aee9fe4"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.5.10"
+            "version": "==1.5.13"
         },
         "idna": {
             "hashes": [
@@ -778,11 +1032,11 @@
         },
         "ipykernel": {
             "hashes": [
-                "sha256:63b4b96c513e1138874934e3e783a8e5e13c02b9036e37107bfe042ac8955005",
-                "sha256:e20ceb7e52cb4d250452e1230be76e0b2323f33bd46c6b2bc7abb6601740e182"
+                "sha256:4ed205700001a83b5832d4821c46a5733f1bf4b1c55744314ae3c756be6b6095",
+                "sha256:697103d218e9a8828025af7986e033c89e0b36e2b6eb84a5bda4739b9a27f3cb"
             ],
-            "markers": "python_version >= '3.5'",
-            "version": "==5.4.2"
+            "index": "pypi",
+            "version": "==5.4.3"
         },
         "ipython": {
             "hashes": [
@@ -801,11 +1055,11 @@
         },
         "jedi": {
             "hashes": [
-                "sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20",
-                "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"
+                "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93",
+                "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
-            "version": "==0.17.2"
+            "markers": "python_version >= '3.6'",
+            "version": "==0.18.0"
         },
         "jinja2": {
             "hashes": [
@@ -831,11 +1085,11 @@
         },
         "jupyter-client": {
             "hashes": [
-                "sha256:49e390b36fe4b4226724704ea28d9fb903f1a3601b6882ce3105221cd09377a1",
-                "sha256:c958d24d6eacb975c1acebb68ac9077da61b5f5c040f22f6849928ad7393b950"
+                "sha256:5eaaa41df449167ebba5e1cf6ca9b31f7fd4f71625069836e2e4fee07fe3cb13",
+                "sha256:649ca3aca1e28f27d73ef15868a7c7f10d6e70f761514582accec3ca6bb13085"
             ],
             "markers": "python_version >= '3.5'",
-            "version": "==6.1.7"
+            "version": "==6.1.11"
         },
         "jupyter-core": {
             "hashes": [
@@ -847,19 +1101,19 @@
         },
         "jupyter-server": {
             "hashes": [
-                "sha256:1c684fa73cad376b09f307ce817be50a50bca78c8be52aa059ec5481098dba1f",
-                "sha256:23ce959718592ba472db7982a5daf15dda3397fd50bb54d05ad10c09fe122905"
+                "sha256:26a98cd5c45b8ebd1e10215586c350a8fa3ca2971e757ee6bf517a180f9933ae",
+                "sha256:49fd3f9f6f4e866c2b8d7494baa2b6e6a7e44236006e443f2c04c407f7f55918"
             ],
             "markers": "python_version >= '3.6'",
-            "version": "==1.1.3"
+            "version": "==1.2.2"
         },
         "jupyterlab": {
             "hashes": [
-                "sha256:15228dff3f77b0bca795fd232cb25f02121510cec83f1d25856b3bc8e585b087",
-                "sha256:42cf1b8c7ebe4e2a502f8538b852c3d553ddde21cb5da6085c4b6bfe67b34fa6"
+                "sha256:ad6337a3fc86e9b2a1c29fca82dfd49a75148ca28b695c94962d7808d968f64d",
+                "sha256:ea75d43d9a054e9192b78ae1eefa72270818d1d787ec21f19db1a92d5cc8db35"
             ],
             "index": "pypi",
-            "version": "==3.0.0"
+            "version": "==3.0.5"
         },
         "jupyterlab-pygments": {
             "hashes": [
@@ -870,11 +1124,11 @@
         },
         "jupyterlab-server": {
             "hashes": [
-                "sha256:1350c36954d3d16c71129b30b60b9df11e8fcf2f3acf88596f6abc8a79b0c918",
-                "sha256:20a4e495276956528783c0befab58e409d9f6258589caccb4c0591387caadc84"
+                "sha256:8e9f35ac7ac411d0c94b66e7d72ca1ef7f2d33ab78090523adcfa42afe862940",
+                "sha256:a71ebeb89eb2ab49eca41768f9840bb6896c264203ea755990313d4dfa610a74"
             ],
             "markers": "python_version >= '3.6'",
-            "version": "==2.0.0"
+            "version": "==2.1.2"
         },
         "markupsafe": {
             "hashes": [
@@ -931,11 +1185,11 @@
         },
         "nbclassic": {
             "hashes": [
-                "sha256:96ffc2b5e01e06825c7558066fc66f2dff00fc7a499ad4988738fda076d5587a",
-                "sha256:e6da2116ab76a63de62f42cf8ea93c9a0c564aaf8315834f7c52efb85b4640ab"
+                "sha256:0248333262d6f90c2fbe05aacb4f008f1d71b5250a9f737488e0a03cfa1c6ed5",
+                "sha256:b649436ff85dc731ba8115deef089e5abbe827d7a6dccbad42c15b8d427104e8"
             ],
             "markers": "python_version >= '3.6'",
-            "version": "==0.2.5"
+            "version": "==0.2.6"
         },
         "nbclient": {
             "hashes": [
@@ -955,11 +1209,11 @@
         },
         "nbformat": {
             "hashes": [
-                "sha256:aa9450c16d29286dc69b92ea4913c1bffe86488f90184445996ccc03a2f60382",
-                "sha256:f545b22138865bfbcc6b1ffe89ed5a2b8e2dc5d4fe876f2ca60d8e6f702a30f8"
+                "sha256:1d223e64a18bfa7cdf2db2e9ba8a818312fc2a0701d2e910b58df66809385a56",
+                "sha256:3949fdc8f5fa0b1afca16fb307546e78494fa7a7bceff880df8168eafda0e7ac"
             ],
             "markers": "python_version >= '3.5'",
-            "version": "==5.0.8"
+            "version": "==5.1.2"
         },
         "nest-asyncio": {
             "hashes": [
@@ -986,51 +1240,51 @@
         },
         "notebook": {
             "hashes": [
-                "sha256:cf40d4f81541401db5a2fda1707ca7877157abd41f04ef7b88f02b67f3c61791",
-                "sha256:e6a62188e319a5d45dd2ed24719f646adf88bef8be1f654ebd0ab360ece6d7a6"
+                "sha256:0464b28e18e7a06cec37e6177546c2322739be07962dd13bf712bcb88361f013",
+                "sha256:25ad93c982b623441b491e693ef400598d1a46cdf11b8c9c0b3be6c61ebbb6cd"
             ],
             "markers": "python_version >= '3.5'",
-            "version": "==6.1.6"
+            "version": "==6.2.0"
         },
         "numpy": {
             "hashes": [
-                "sha256:08308c38e44cc926bdfce99498b21eec1f848d24c302519e64203a8da99a97db",
-                "sha256:09c12096d843b90eafd01ea1b3307e78ddd47a55855ad402b157b6c4862197ce",
-                "sha256:13d166f77d6dc02c0a73c1101dd87fdf01339febec1030bd810dcd53fff3b0f1",
-                "sha256:141ec3a3300ab89c7f2b0775289954d193cc8edb621ea05f99db9cb181530512",
-                "sha256:16c1b388cc31a9baa06d91a19366fb99ddbe1c7b205293ed072211ee5bac1ed2",
-                "sha256:18bed2bcb39e3f758296584337966e68d2d5ba6aab7e038688ad53c8f889f757",
-                "sha256:1aeef46a13e51931c0b1cf8ae1168b4a55ecd282e6688fdb0a948cc5a1d5afb9",
-                "sha256:27d3f3b9e3406579a8af3a9f262f5339005dd25e0ecf3cf1559ff8a49ed5cbf2",
-                "sha256:2a2740aa9733d2e5b2dfb33639d98a64c3b0f24765fed86b0fd2aec07f6a0a08",
-                "sha256:4377e10b874e653fe96985c05feed2225c912e328c8a26541f7fc600fb9c637b",
-                "sha256:448ebb1b3bf64c0267d6b09a7cba26b5ae61b6d2dbabff7c91b660c7eccf2bdb",
-                "sha256:50e86c076611212ca62e5a59f518edafe0c0730f7d9195fec718da1a5c2bb1fc",
-                "sha256:5734bdc0342aba9dfc6f04920988140fb41234db42381cf7ccba64169f9fe7ac",
-                "sha256:64324f64f90a9e4ef732be0928be853eee378fd6a01be21a0a8469c4f2682c83",
-                "sha256:6ae6c680f3ebf1cf7ad1d7748868b39d9f900836df774c453c11c5440bc15b36",
-                "sha256:6d7593a705d662be5bfe24111af14763016765f43cb6923ed86223f965f52387",
-                "sha256:8cac8790a6b1ddf88640a9267ee67b1aee7a57dfa2d2dd33999d080bc8ee3a0f",
-                "sha256:8ece138c3a16db8c1ad38f52eb32be6086cc72f403150a79336eb2045723a1ad",
-                "sha256:9eeb7d1d04b117ac0d38719915ae169aa6b61fca227b0b7d198d43728f0c879c",
-                "sha256:a09f98011236a419ee3f49cedc9ef27d7a1651df07810ae430a6b06576e0b414",
-                "sha256:a5d897c14513590a85774180be713f692df6fa8ecf6483e561a6d47309566f37",
-                "sha256:ad6f2ff5b1989a4899bf89800a671d71b1612e5ff40866d1f4d8bcf48d4e5764",
-                "sha256:c42c4b73121caf0ed6cd795512c9c09c52a7287b04d105d112068c1736d7c753",
-                "sha256:cb1017eec5257e9ac6209ac172058c430e834d5d2bc21961dceeb79d111e5909",
-                "sha256:d6c7bb82883680e168b55b49c70af29b84b84abb161cbac2800e8fcb6f2109b6",
-                "sha256:e452dc66e08a4ce642a961f134814258a082832c78c90351b75c41ad16f79f63",
-                "sha256:e5b6ed0f0b42317050c88022349d994fe72bfe35f5908617512cd8c8ef9da2a9",
-                "sha256:e9b30d4bd69498fc0c3fe9db5f62fffbb06b8eb9321f92cc970f2969be5e3949",
-                "sha256:ec149b90019852266fec2341ce1db513b843e496d5a8e8cdb5ced1923a92faab",
-                "sha256:edb01671b3caae1ca00881686003d16c2209e07b7ef8b7639f1867852b948f7c",
-                "sha256:f0d3929fe88ee1c155129ecd82f981b8856c5d97bcb0d5f23e9b4242e79d1de3",
-                "sha256:f29454410db6ef8126c83bd3c968d143304633d45dc57b51252afbd79d700893",
-                "sha256:fe45becb4c2f72a0907c1d0246ea6449fe7a9e2293bb0e11c4e9a32bb0930a15",
-                "sha256:fedbd128668ead37f33917820b704784aff695e0019309ad446a6d0b065b57e4"
+                "sha256:012426a41bc9ab63bb158635aecccc7610e3eff5d31d1eb43bc099debc979d94",
+                "sha256:06fab248a088e439402141ea04f0fffb203723148f6ee791e9c75b3e9e82f080",
+                "sha256:0eef32ca3132a48e43f6a0f5a82cb508f22ce5a3d6f67a8329c81c8e226d3f6e",
+                "sha256:1ded4fce9cfaaf24e7a0ab51b7a87be9038ea1ace7f34b841fe3b6894c721d1c",
+                "sha256:2e55195bc1c6b705bfd8ad6f288b38b11b1af32f3c8289d6c50d47f950c12e76",
+                "sha256:2ea52bd92ab9f768cc64a4c3ef8f4b2580a17af0a5436f6126b08efbd1838371",
+                "sha256:36674959eed6957e61f11c912f71e78857a8d0604171dfd9ce9ad5cbf41c511c",
+                "sha256:384ec0463d1c2671170901994aeb6dce126de0a95ccc3976c43b0038a37329c2",
+                "sha256:39b70c19ec771805081578cc936bbe95336798b7edf4732ed102e7a43ec5c07a",
+                "sha256:400580cbd3cff6ffa6293df2278c75aef2d58d8d93d3c5614cd67981dae68ceb",
+                "sha256:43d4c81d5ffdff6bae58d66a3cd7f54a7acd9a0e7b18d97abb255defc09e3140",
+                "sha256:50a4a0ad0111cc1b71fa32dedd05fa239f7fb5a43a40663269bb5dc7877cfd28",
+                "sha256:603aa0706be710eea8884af807b1b3bc9fb2e49b9f4da439e76000f3b3c6ff0f",
+                "sha256:6149a185cece5ee78d1d196938b2a8f9d09f5a5ebfbba66969302a778d5ddd1d",
+                "sha256:759e4095edc3c1b3ac031f34d9459fa781777a93ccc633a472a5468587a190ff",
+                "sha256:7fb43004bce0ca31d8f13a6eb5e943fa73371381e53f7074ed21a4cb786c32f8",
+                "sha256:811daee36a58dc79cf3d8bdd4a490e4277d0e4b7d103a001a4e73ddb48e7e6aa",
+                "sha256:8b5e972b43c8fc27d56550b4120fe6257fdc15f9301914380b27f74856299fea",
+                "sha256:99abf4f353c3d1a0c7a5f27699482c987cf663b1eac20db59b8c7b061eabd7fc",
+                "sha256:a0d53e51a6cb6f0d9082decb7a4cb6dfb33055308c4c44f53103c073f649af73",
+                "sha256:a12ff4c8ddfee61f90a1633a4c4afd3f7bcb32b11c52026c92a12e1325922d0d",
+                "sha256:a4646724fba402aa7504cd48b4b50e783296b5e10a524c7a6da62e4a8ac9698d",
+                "sha256:a76f502430dd98d7546e1ea2250a7360c065a5fdea52b2dffe8ae7180909b6f4",
+                "sha256:a9d17f2be3b427fbb2bce61e596cf555d6f8a56c222bd2ca148baeeb5e5c783c",
+                "sha256:ab83f24d5c52d60dbc8cd0528759532736b56db58adaa7b5f1f76ad551416a1e",
+                "sha256:aeb9ed923be74e659984e321f609b9ba54a48354bfd168d21a2b072ed1e833ea",
+                "sha256:c843b3f50d1ab7361ca4f0b3639bf691569493a56808a0b0c54a051d260b7dbd",
+                "sha256:cae865b1cae1ec2663d8ea56ef6ff185bad091a5e33ebbadd98de2cfa3fa668f",
+                "sha256:cc6bd4fd593cb261332568485e20a0712883cf631f6f5e8e86a52caa8b2b50ff",
+                "sha256:cf2402002d3d9f91c8b01e66fbb436a4ed01c6498fffed0e4c7566da1d40ee1e",
+                "sha256:d051ec1c64b85ecc69531e1137bb9751c6830772ee5c1c426dbcfe98ef5788d7",
+                "sha256:d6631f2e867676b13026e2846180e2c13c1e11289d67da08d71cacb2cd93d4aa",
+                "sha256:dbd18bcf4889b720ba13a27ec2f2aac1981bd41203b3a3b27ba7a33f88ae4827",
+                "sha256:df609c82f18c5b9f6cb97271f03315ff0dbe481a2a02e56aeb1b1a985ce38e60"
             ],
             "markers": "python_version >= '3.6'",
-            "version": "==1.19.4"
+            "version": "==1.19.5"
         },
         "obonet": {
             "hashes": [
@@ -1050,33 +1304,26 @@
         },
         "pandas": {
             "hashes": [
-                "sha256:0a643bae4283a37732ddfcecab3f62dd082996021b980f580903f4e8e01b3c5b",
-                "sha256:0de3ddb414d30798cbf56e642d82cac30a80223ad6fe484d66c0ce01a84d6f2f",
-                "sha256:19a2148a1d02791352e9fa637899a78e371a3516ac6da5c4edc718f60cbae648",
-                "sha256:21b5a2b033380adbdd36b3116faaf9a4663e375325831dac1b519a44f9e439bb",
-                "sha256:24c7f8d4aee71bfa6401faeba367dd654f696a77151a8a28bc2013f7ced4af98",
-                "sha256:26fa92d3ac743a149a31b21d6f4337b0594b6302ea5575b37af9ca9611e8981a",
-                "sha256:2860a97cbb25444ffc0088b457da0a79dc79f9c601238a3e0644312fcc14bf11",
-                "sha256:2b1c6cd28a0dfda75c7b5957363333f01d370936e4c6276b7b8e696dd500582a",
-                "sha256:2c2f7c670ea4e60318e4b7e474d56447cf0c7d83b3c2a5405a0dbb2600b9c48e",
-                "sha256:3be7a7a0ca71a2640e81d9276f526bca63505850add10206d0da2e8a0a325dae",
-                "sha256:4c62e94d5d49db116bef1bd5c2486723a292d79409fc9abd51adf9e05329101d",
-                "sha256:5008374ebb990dad9ed48b0f5d0038124c73748f5384cc8c46904dace27082d9",
-                "sha256:5447ea7af4005b0daf695a316a423b96374c9c73ffbd4533209c5ddc369e644b",
-                "sha256:573fba5b05bf2c69271a32e52399c8de599e4a15ab7cec47d3b9c904125ab788",
-                "sha256:5a780260afc88268a9d3ac3511d8f494fdcf637eece62fb9eb656a63d53eb7ca",
-                "sha256:70865f96bb38fec46f7ebd66d4b5cfd0aa6b842073f298d621385ae3898d28b5",
-                "sha256:731568be71fba1e13cae212c362f3d2ca8932e83cb1b85e3f1b4dd77d019254a",
-                "sha256:b61080750d19a0122469ab59b087380721d6b72a4e7d962e4d7e63e0c4504814",
-                "sha256:bf23a3b54d128b50f4f9d4675b3c1857a688cc6731a32f931837d72effb2698d",
-                "sha256:c16d59c15d946111d2716856dd5479221c9e4f2f5c7bc2d617f39d870031e086",
-                "sha256:c61c043aafb69329d0f961b19faa30b1dab709dd34c9388143fc55680059e55a",
-                "sha256:c94ff2780a1fd89f190390130d6d36173ca59fcfb3fe0ff596f9a56518191ccb",
-                "sha256:edda9bacc3843dfbeebaf7a701763e68e741b08fccb889c003b0a52f0ee95782",
-                "sha256:f10fc41ee3c75a474d3bdf68d396f10782d013d7f67db99c0efbfd0acb99701b"
-            ],
-            "markers": "python_full_version >= '3.6.1'",
-            "version": "==1.1.5"
+                "sha256:050ed2c9d825ef36738e018454e6d055c63d947c1d52010fbadd7584f09df5db",
+                "sha256:055647e7f4c5e66ba92c2a7dcae6c2c57898b605a3fb007745df61cc4015937f",
+                "sha256:23ac77a3a222d9304cb2a7934bb7b4805ff43d513add7a42d1a22dc7df14edd2",
+                "sha256:2de012a36cc507debd9c3351b4d757f828d5a784a5fc4e6766eafc2b56e4b0f5",
+                "sha256:30e9e8bc8c5c17c03d943e8d6f778313efff59e413b8dbdd8214c2ed9aa165f6",
+                "sha256:324e60bea729cf3b55c1bf9e88fe8b9932c26f8669d13b928e3c96b3a1453dff",
+                "sha256:37443199f451f8badfe0add666e43cdb817c59fa36bceedafd9c543a42f236ca",
+                "sha256:47ec0808a8357ab3890ce0eca39a63f79dcf941e2e7f494470fe1c9ec43f6091",
+                "sha256:496fcc29321e9a804d56d5aa5d7ec1320edfd1898eee2f451aa70171cf1d5a29",
+                "sha256:50e6c0a17ef7f831b5565fd0394dbf9bfd5d615ee4dd4bb60a3d8c9d2e872323",
+                "sha256:5527c5475d955c0bc9689c56865aaa2a7b13c504d6c44f0aadbf57b565af5ebd",
+                "sha256:57d5c7ac62925a8d2ab43ea442b297a56cc8452015e71e24f4aa7e4ed6be3d77",
+                "sha256:9d45f58b03af1fea4b48e44aa38a819a33dccb9821ef9e1d68f529995f8a632f",
+                "sha256:b26e2dabda73d347c7af3e6fed58483161c7b87a886a4e06d76ccfe55a044aa9",
+                "sha256:cfd237865d878da9b65cfee883da5e0067f5e2ff839e459466fb90565a77bda3",
+                "sha256:d7cca42dba13bfee369e2944ae31f6549a55831cba3117e17636955176004088",
+                "sha256:fe7de6fed43e7d086e3d947651ec89e55ddf00102f9dd5758763d56d182f0564"
+            ],
+            "markers": "python_full_version >= '3.7.1'",
+            "version": "==1.2.1"
         },
         "pandocfilters": {
             "hashes": [
@@ -1086,11 +1333,11 @@
         },
         "parso": {
             "hashes": [
-                "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea",
-                "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"
+                "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410",
+                "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==0.7.1"
+            "markers": "python_version >= '3.6'",
+            "version": "==0.8.1"
         },
         "pexpect": {
             "hashes": [
@@ -1132,19 +1379,18 @@
         },
         "prompt-toolkit": {
             "hashes": [
-                "sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c",
-                "sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63"
+                "sha256:0bdd2585e5afd00c5f91dd28eb2090ea67e94c385878921939bb4ccfa3904723",
+                "sha256:dc83e6368b0edd9ceabe17a055f2e22f6ed95b9aa39dbd59d0b4f3585bdfe9ed"
             ],
             "markers": "python_full_version >= '3.6.1'",
-            "version": "==3.0.8"
+            "version": "==3.0.11"
         },
         "ptyprocess": {
             "hashes": [
-                "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0",
-                "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"
+                "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35",
+                "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"
             ],
-            "markers": "os_name != 'nt'",
-            "version": "==0.6.0"
+            "version": "==0.7.0"
         },
         "py": {
             "hashes": [
@@ -1188,18 +1434,18 @@
         },
         "pygments": {
             "hashes": [
-                "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716",
-                "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"
+                "sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435",
+                "sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"
             ],
             "markers": "python_version >= '3.5'",
-            "version": "==2.7.3"
+            "version": "==2.7.4"
         },
         "pyparsing": {
             "hashes": [
                 "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
                 "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
             ],
-            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
+            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==2.4.7"
         },
         "pyrsistent": {
@@ -1231,79 +1477,87 @@
         },
         "pytest-cov": {
             "hashes": [
-                "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191",
-                "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"
+                "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7",
+                "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"
             ],
             "index": "pypi",
-            "version": "==2.10.1"
+            "version": "==2.11.1"
         },
         "python-dateutil": {
             "hashes": [
                 "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
                 "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==2.8.1"
         },
         "pytz": {
             "hashes": [
-                "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268",
-                "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"
+                "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4",
+                "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"
             ],
-            "version": "==2020.4"
+            "version": "==2020.5"
         },
         "pyyaml": {
             "hashes": [
-                "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
-                "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
-                "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
-                "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e",
-                "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
-                "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
-                "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
-                "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
-                "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
-                "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a",
-                "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
-                "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
-                "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
+                "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf",
+                "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696",
+                "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393",
+                "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77",
+                "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922",
+                "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5",
+                "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8",
+                "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10",
+                "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc",
+                "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
+                "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
+                "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
+                "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
+                "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
+                "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
+                "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
+                "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
+                "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
+                "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
+                "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
+                "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"
             ],
-            "version": "==5.3.1"
+            "index": "pypi",
+            "version": "==5.4.1"
         },
         "pyzmq": {
             "hashes": [
-                "sha256:03638e46d486dd1c118e03c8bf9c634bdcae679600eac6573ae1e54906de7c2f",
-                "sha256:0af84f34f27b5c6a0e906c648bdf46d4caebf9c8e6e16db0728f30a58141cad6",
-                "sha256:0e554fd390021edbe0330b67226325a820b0319c5b45e1b0a59bf22ccc36e793",
-                "sha256:1e9b75a119606732023a305d1c214146c09a91f8116f6aff3e8b7d0a60b6f0ff",
-                "sha256:225774a48ed7414c0395335e7123ef8c418dbcbe172caabdc2496133b03254c2",
-                "sha256:2742e380d186673eee6a570ef83d4568741945434ba36d92b98d36cdbfedbd44",
-                "sha256:309d763d89ec1845c0e0fa14e1fb6558fd8c9ef05ed32baec27d7a8499cc7bb0",
-                "sha256:46250789730489009fe139cbf576679557c070a6a3628077d09a4153d52fd381",
-                "sha256:4d9259a5eb3f71abbaf61f165cacf42240bfeea3783bebd8255341abdfe206f1",
-                "sha256:523d542823cabb94065178090e05347bd204365f6e7cb260f0071c995d392fc2",
-                "sha256:53706f4a792cdae422121fb6a5e65119bad02373153364fc9d004cf6a90394de",
-                "sha256:5efe02bdcc5eafcac0aab531292294298f0ab8d28ed43be9e507d0e09173d1a4",
-                "sha256:63ee08e35be72fdd7568065a249a5b5cf51a2e8ab6ee63cf9f73786fcb9e710b",
-                "sha256:6e24907857c80dc67692e31f5bf3ad5bf483ee0142cec95b3d47e2db8c43bdda",
-                "sha256:7113eb93dcd0a5750c65d123ed0099e036a3a3f2dcb48afedd025ffa125c983b",
-                "sha256:824ad5888331aadeac772bce27e1c2fbcab82fade92edbd234542c4e12f0dca9",
-                "sha256:895695be380f0f85d2e3ec5ccf68a93c92d45bd298567525ad5633071589872c",
-                "sha256:b62113eeb9a0649cebed9b21fd578f3a0175ef214a2a91dcb7b31bbf55805295",
-                "sha256:bc7dd697356b31389d5118b9bcdef3e8d8079e8181800c4e8d72dccd56e1ff68",
-                "sha256:bf755905a7d30d2749079611b9a89924c1f2da2695dc09ce221f42122c9808e3",
-                "sha256:c63fafd2556d218368c51d18588f8e6f8d86d09d493032415057faf6de869b34",
-                "sha256:c95dda497a7c1b1e734b5e8353173ca5dd7b67784d8821d13413a97856588057",
-                "sha256:cc09c5cd1a4332611c8564d65e6a432dc6db3e10793d0254da9fa1e31d9ffd6d",
-                "sha256:cfa54a162a7b32641665e99b2c12084555afe9fc8fe80ec8b2f71a57320d10e1",
-                "sha256:d81184489369ec325bd50ba1c935361e63f31f578430b9ad95471899361a8253",
-                "sha256:d92c7f41a53ece82b91703ea433c7d34143248cf0cead33aa11c5fc621c764bf",
-                "sha256:dc2f48b575dff6edefd572f1ac84cf0c3f18ad5fcf13384de32df740a010594a",
-                "sha256:f0beef935efe78a63c785bb21ed56c1c24448511383e3994927c8bb2caf5e714",
-                "sha256:f110a4d3f8f01209eec304ed542f6c8054cce9b0f16dfe3d571e57c290e4e133"
+                "sha256:01715453ce14d4b804f87969461d21fff47df9bebde3c283c1ad872207717abc",
+                "sha256:083dd4c1e9bc058acabab5d95e25180cec224ca9d372b088bf204b0822b278a9",
+                "sha256:20c53aff015001cb705db0928850fa74ea4280a935d4e726743e4cb13206b0f2",
+                "sha256:2199156013875ff4f872daa86214fe34658e4017b5cd8c4a2c4d6d9b59d1a2eb",
+                "sha256:405e754799480d960df7d8249192c4e46288d41d08aaaa45f339269bc09f3c0a",
+                "sha256:520a80148c26cfbfb76fd169c089e7a899071dd5cd7553269e4da149382b9b88",
+                "sha256:5adc4e3015c647e413bdcf3cac803ffdb8566b938f83e5234ab9c2c14fe3ea3a",
+                "sha256:69e5c1061a2e99ac2647db271a41cb5c95ff62dd5090a948b1fbca905c5cba81",
+                "sha256:76e1b4dff2be48ed98ec34dd10ad97316e69cb5ff37754f84abc9fb4bbc949bc",
+                "sha256:77371c7a39d2f1b71444128b9377be8b0588c3fbf7f56db970c5d4b7af8ed9fd",
+                "sha256:7ca684fdb433577c30243357813eef81973d5dbbc3c6c1568e6c21ec1dcedda3",
+                "sha256:7ea55c672840ee8fd5884134c0697845d28f5b053713fc682b5d5fc73d747853",
+                "sha256:8f17f71430c18666c0f6c81185ef494f59231d01b1f77f67debfe628d50479c6",
+                "sha256:9026acf8bf0852c8360c574d04d22d7a213dafaf04ab9c4d43c7430eda272cdd",
+                "sha256:923ec92c7b82d63bab4193aee23fd4a2b1636369494d55883fbda10fef1075a3",
+                "sha256:930e33d92e7d991a1c194790c7fc7f3099f7ec1447e853b4218cba914bee3b7b",
+                "sha256:a2b9e25ea0f81e920de3bff65a5bd9056acd81f8cb439546d00d77f386cba251",
+                "sha256:a82f6f41523db5408925b82bb150ecbc625c2eeccf31d38fa1a0e395e11dd5e2",
+                "sha256:b1fb293a5562a4870f20bb859a50bd59c14fdb1fc13353e25267facaf68f6eb0",
+                "sha256:b2a5d5fd2857e5006a5fd9067f5aa7aff0cd4f994180681b13a6bd724a5ce289",
+                "sha256:c12fba29f0b956390aed37d463fbea215d7592c08241fb20a2c165ef64c95019",
+                "sha256:c3a630dd7716e8e127d43b22598e256a2d11a847b8cc3310350528960037fa06",
+                "sha256:de00a0fe9735efa06b96af56c8e7baa67c0972ec510e18c98efbb593c73cd886",
+                "sha256:e51ea97103791597e4deca13992c3544224c7eed89dc575d9a85972b16f01b59",
+                "sha256:e98d9b9efb22ece82b06046ba0c00cce157cbfd852cbd9a385b338f295cf38e6",
+                "sha256:f1e357e234b435441b9366f6958623abe74fbbb1bd8e3bc679f09b5126785785",
+                "sha256:f321b1e2ea990e9e760c1894234ee426e150995691c05b840a0d9743f5f202e1",
+                "sha256:fe0186c70fd3205b31daaa024409b8887af9b0344f47bc4d5ed03f08f64b9552"
             ],
-            "markers": "python_version >= '3.5'",
-            "version": "==20.0.0"
+            "markers": "python_version >= '3.6'",
+            "version": "==21.0.1"
         },
         "requests": {
             "hashes": [
@@ -1325,7 +1579,7 @@
                 "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
                 "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==1.15.0"
         },
         "sniffio": {
@@ -1338,18 +1592,18 @@
         },
         "snowballstemmer": {
             "hashes": [
-                "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0",
-                "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"
+                "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2",
+                "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"
             ],
-            "version": "==2.0.0"
+            "version": "==2.1.0"
         },
         "terminado": {
             "hashes": [
-                "sha256:3da72a155b807b01c9e8a5babd214e052a0a45a975751da3521a1c3381ce6d76",
-                "sha256:c55f025beb06c2e2669f7ba5a04f47bb3304c30c05842d4981d8f0fc9ab3b4e3"
+                "sha256:23a053e06b22711269563c8bb96b36a036a86be8b5353e85e804f89b84aaa23f",
+                "sha256:89e6d94b19e4bc9dce0ffd908dfaf55cc78a9bf735934e915a4a96f65ac9704c"
             ],
             "markers": "python_version >= '3.6'",
-            "version": "==0.9.1"
+            "version": "==0.9.2"
         },
         "testpath": {
             "hashes": [
@@ -1367,7 +1621,7 @@
                 "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
                 "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
             ],
-            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
+            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==0.10.2"
         },
         "tornado": {
@@ -1441,11 +1695,11 @@
         },
         "virtualenv": {
             "hashes": [
-                "sha256:54b05fc737ea9c9ee9f8340f579e5da5b09fb64fd010ab5757eb90268616907c",
-                "sha256:b7a8ec323ee02fb2312f098b6b4c9de99559b462775bc8fe3627a73706603c1b"
+                "sha256:219ee956e38b08e32d5639289aaa5bd190cfbe7dafcb8fa65407fca08e808f9c",
+                "sha256:227a8fed626f2f20a6cdb0870054989f82dd27b2560a911935ba905a2a5e0034"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==20.2.2"
+            "version": "==20.4.0"
         },
         "wcwidth": {
             "hashes": [
diff --git a/cli.py b/cli.py
index b928bcfc..4398899f 100644
--- a/cli.py
+++ b/cli.py
@@ -1,7 +1,7 @@
 """This module provides a CLI util to make updates to normalizer database."""
 import click
 from botocore.exceptions import ClientError
-from therapy.etl import ChEMBL, Wikidata, DrugBank, NCIt
+from therapy.etl import ChEMBL, Wikidata, DrugBank, NCIt, ChemIDplus, RxNorm
 from therapy.schemas import SourceName
 from timeit import default_timer as timer
 from therapy.database import Database
@@ -38,7 +38,9 @@ def update_normalizer_db(normalizer, dev, db_url, update_all):
             'chembl': ChEMBL,
             'ncit': NCIt,
             'wikidata': Wikidata,
-            'drugbank': DrugBank
+            'drugbank': DrugBank,
+            'chemidplus': ChemIDplus,
+            'rxnorm': RxNorm,
         }
 
         if dev:
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 00000000..71402dea
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,139 @@
+#
+# These requirements were autogenerated by pipenv
+# To regenerate from the project's Pipfile, run:
+#
+#    pipenv lock --requirements --dev
+#
+
+# Note: in pipenv 2020.x, "--dev" changed to emit both default and development
+# requirements. To emit only development requirements, pass "--dev-only".
+
+-i https://pypi.org/simple
+-e .
+anyio==2.0.2; python_full_version >= '3.6.2'
+appdirs==1.4.4
+appnope==0.1.2; sys_platform == 'darwin' and platform_system == 'Darwin'
+argon2-cffi==20.1.0
+async-generator==1.10; python_version >= '3.5'
+attrs==20.3.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+babel==2.9.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+backcall==0.2.0
+backports-datetime-fromisoformat==1.0.0
+beautifulsoup4==4.9.3
+bleach==3.2.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
+boto3==1.16.54
+botocore==1.19.54
+bs4==0.0.1
+certifi==2020.12.5
+cffi==1.14.4
+cfgv==3.2.0; python_full_version >= '3.6.1'
+chardet==4.0.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
+civicpy==1.1.2
+click==7.1.2
+colorama==0.4.4; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
+coverage==5.3.1
+coveralls==3.0.0
+cython==0.29.21
+decorator==4.4.2
+defusedxml==0.6.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
+deprecation==2.1.0
+distlib==0.3.1
+docopt==0.6.2
+docutils==0.16; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
+entrypoints==0.3; python_version >= '2.7'
+fastapi==0.63.0
+filelock==3.0.12
+flake8-docstrings==1.5.0
+flake8==3.8.4
+h11==0.12.0; python_version >= '3.6'
+httptools==0.1.1
+identify==1.5.12; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+idna==2.10; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+iniconfig==1.1.1
+ipykernel==5.4.3
+ipython-genutils==0.2.0
+ipython==7.19.0; python_version >= '3.7'
+isodate==0.6.0
+jedi==0.18.0; python_version >= '3.6'
+jinja2==2.11.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
+jmespath==0.10.0; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
+json5==0.9.5
+jsonschema==3.2.0
+jupyter-client==6.1.11; python_version >= '3.5'
+jupyter-core==4.7.0; python_version >= '3.6'
+jupyter-server==1.2.1; python_version >= '3.6'
+jupyterlab-pygments==0.1.2
+jupyterlab-server==2.1.2; python_version >= '3.6'
+jupyterlab==3.0.4
+keyring==21.8.0; python_version >= '3.6'
+lxml==4.6.2
+markupsafe==1.1.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+mccabe==0.6.1
+mistune==0.8.4
+nbclassic==0.2.6; python_version >= '3.6'
+nbclient==0.5.1; python_version >= '3.6'
+nbconvert==6.0.7; python_version >= '3.6'
+nbformat==5.1.1; python_version >= '3.5'
+nest-asyncio==1.4.3; python_version >= '3.5'
+networkx==2.5; python_version >= '3.6'
+nodeenv==1.5.0
+notebook==6.2.0; python_version >= '3.5'
+numpy==1.19.5; python_version >= '3.6'
+obonet==0.2.6; python_version >= '3'
+owlready2==0.26
+packaging==20.8; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+pandas==1.2.0; python_full_version >= '3.7.1'
+pandocfilters==1.4.3
+parso==0.8.1; python_version >= '3.6'
+pexpect==4.8.0; sys_platform != 'win32'
+pickleshare==0.7.5
+pkginfo==1.6.1
+pluggy==0.13.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+pre-commit==2.9.3
+prometheus-client==0.9.0
+prompt-toolkit==3.0.10; python_full_version >= '3.6.1'
+ptyprocess==0.7.0
+py==1.10.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+pycodestyle==2.6.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+pycparser==2.20; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+pydantic==1.7.3
+pydocstyle==5.1.1; python_version >= '3.5'
+pyflakes==2.2.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+pygments==2.7.4; python_version >= '3.5'
+pyparsing==2.4.7; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
+pyrsistent==0.17.3; python_version >= '3.5'
+pysam==0.16.0.1
+pytest-cov==2.10.1
+pytest==6.2.1
+python-dateutil==2.8.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+pytz==2020.5
+pyyaml==5.3.1
+pyzmq==21.0.0; python_version >= '3.6'
+rdflib==5.0.0
+readme-renderer==28.0
+requests-toolbelt==0.9.1
+requests==2.25.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
+rfc3986==1.4.0
+s3transfer==0.3.4
+send2trash==1.5.0
+six==1.15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+sniffio==1.2.0; python_version >= '3.5'
+snowballstemmer==2.0.0
+soupsieve==2.1; python_version >= '3.0'
+starlette==0.13.6; python_version >= '3.6'
+terminado==0.9.2; python_version >= '3.6'
+testpath==0.4.4
+toml==0.10.2; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
+tornado==6.1; python_version >= '3.5'
+tqdm==4.56.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+traitlets==5.0.5; python_version >= '3.7'
+twine==3.3.0
+typing-extensions==3.7.4.3
+urllib3==1.26.2; python_version != '3.4'
+uvicorn==0.13.3
+uvloop==0.14.0
+vcfpy==0.13.3
+virtualenv==20.3.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+wcwidth==0.2.5
+webencodings==0.5.1
+websockets==8.1
diff --git a/requirements.txt b/requirements.txt
index 0a01f740..71ee71ea 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,43 +6,64 @@
 #
 
 -i https://pypi.org/simple
+appnope==0.1.2; sys_platform == 'darwin' and platform_system == 'Darwin'
+backcall==0.2.0
+beautifulsoup4==4.9.3
 bleach==3.2.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
-boto3==1.16.43
-botocore==1.19.43
+boto3==1.16.54
+botocore==1.19.54
+bs4==0.0.1
 certifi==2020.12.5
 chardet==4.0.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
 click==7.1.2
 colorama==0.4.4; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
 cython==0.29.21
+decorator==4.4.2
 docutils==0.16; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
 fastapi==0.63.0
-h11==0.11.0
+h11==0.12.0; python_version >= '3.6'
 httptools==0.1.1
 idna==2.10; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+ipykernel==5.4.3
+ipython-genutils==0.2.0
+ipython==7.19.0; python_version >= '3.7'
 isodate==0.6.0
+jedi==0.18.0; python_version >= '3.6'
 jmespath==0.10.0; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
-keyring==21.7.0; python_version >= '3.6'
+jupyter-client==6.1.11; python_version >= '3.5'
+jupyter-core==4.7.0; python_version >= '3.6'
+keyring==21.8.0; python_version >= '3.6'
 lxml==4.6.2
 owlready2==0.26
 packaging==20.8; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+parso==0.8.1; python_version >= '3.6'
+pexpect==4.8.0; sys_platform != 'win32'
+pickleshare==0.7.5
 pkginfo==1.6.1
+prompt-toolkit==3.0.10; python_full_version >= '3.6.1'
+ptyprocess==0.7.0
 pydantic==1.7.3
-pygments==2.7.3; python_version >= '3.5'
+pygments==2.7.4; python_version >= '3.5'
 pyparsing==2.4.7; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
 python-dateutil==2.8.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+pyzmq==21.0.0; python_version >= '3.6'
 rdflib==5.0.0
 readme-renderer==28.0
 requests-toolbelt==0.9.1
 requests==2.25.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
 rfc3986==1.4.0
-s3transfer==0.3.3
+s3transfer==0.3.4
 six==1.15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+soupsieve==2.1; python_version >= '3.0'
 starlette==0.13.6; python_version >= '3.6'
-tqdm==4.54.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+tornado==6.1; python_version >= '3.5'
+tqdm==4.56.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
+traitlets==5.0.5; python_version >= '3.7'
 twine==3.3.0
 typing-extensions==3.7.4.3
 urllib3==1.26.2; python_version != '3.4'
-uvicorn==0.13.2
+uvicorn==0.13.3
 uvloop==0.14.0
+wcwidth==0.2.5
 webencodings==0.5.1
 websockets==8.1
diff --git a/therapy/__init__.py b/therapy/__init__.py
index 135cf43c..cbbc2a75 100644
--- a/therapy/__init__.py
+++ b/therapy/__init__.py
@@ -3,5 +3,16 @@
 import logging
 
 PROJECT_ROOT = Path(__file__).resolve().parents[1]
+logging.basicConfig(
+    filename='therapy.log',
+    format='[%(asctime)s] %(levelname)s : %(message)s')
 logger = logging.getLogger('therapy')
 logger.setLevel(logging.DEBUG)
+
+
+class DownloadException(Exception):
+    """Exception for failures relating to source file downloads."""
+
+    def __init__(self, *args, **kwargs):
+        """Initialize exception."""
+        super().__init__(*args, **kwargs)
diff --git a/therapy/etl/__init__.py b/therapy/etl/__init__.py
index 7ddf4d78..121a845f 100644
--- a/therapy/etl/__init__.py
+++ b/therapy/etl/__init__.py
@@ -3,3 +3,5 @@
 from .chembl import ChEMBL  # noqa: F401
 from .ncit import NCIt  # noqa: F401
 from .drugbank import DrugBank  # noqa: F401
+from .chemidplus import ChemIDplus  # noqa: F401
+from .rxnorm import RxNorm  # noqa: F401
diff --git a/therapy/etl/base.py b/therapy/etl/base.py
index a5d760fd..5114e3fd 100644
--- a/therapy/etl/base.py
+++ b/therapy/etl/base.py
@@ -3,8 +3,10 @@
 from therapy.schemas import NamespacePrefix
 from therapy.database import Database
 
+# prefixes for translating ID namespaces
 IDENTIFIER_PREFIXES = {
     'casRegistry': NamespacePrefix.CASREGISTRY.value,
+    'ChemIDplus': NamespacePrefix.CHEMIDPLUS.value,  # needed for wikidata
     'pubchemCompound': NamespacePrefix.PUBCHEMCOMPOUND.value,
     'pubchemSubstance': NamespacePrefix.PUBCHEMSUBSTANCE.value,
     'chembl': NamespacePrefix.CHEMBL.value,
diff --git a/therapy/etl/chemidplus.py b/therapy/etl/chemidplus.py
new file mode 100644
index 00000000..02a13328
--- /dev/null
+++ b/therapy/etl/chemidplus.py
@@ -0,0 +1,192 @@
+"""ETL methods for ChemIDPlus® source.
+
+Courtesy of the U.S. National Library of Medicine.
+"""
+from .base import Base
+from therapy import PROJECT_ROOT
+from therapy.database import Database
+from therapy.schemas import Drug, NamespacePrefix, Meta, SourceName, \
+    DataLicenseAttributes
+from pathlib import Path
+from ftplib import FTP
+import xml.etree.ElementTree as ET
+import logging
+from typing import Dict
+from boto3.dynamodb.table import BatchWriter
+import re
+
+
+logger = logging.getLogger('therapy')
+logger.setLevel(logging.DEBUG)
+
+
+TAGS_REGEX = r' \[.*\]'
+
+
+class ChemIDplus(Base):
+    """Core ChemIDplus ETL class."""
+
+    def __init__(self,
+                 database: Database,
+                 data_path: Path = PROJECT_ROOT / 'data' / 'chemidplus',
+                 src_server: str = 'ftp.nlm.nih.gov',
+                 src_dir_path: str = 'nlmdata/.chemidlease/',
+                 src_fname: str = 'CurrentChemID.xml'):
+        """Initialize class instance.
+
+        :param therapy.database.Database database: database instance to use
+        :param Path data_path: path to chemidplus subdirectory in application
+            data folder
+        :param str src_server: The NLM domain
+        :param str src_dir_path: The directory to the chemidplus release
+        :param str src_fname: name of file as stored in src_dir.
+
+        If the source file is provided locally in the data_path directory,
+        it's unnecessary to provide `src_dir` and `src_fname` args.
+        """
+        self.database = database
+        self._src_server = src_server
+        self._src_dir_path = src_dir_path
+        self._src_fname = src_fname
+        self._added_ids = set()
+
+        # perform ETL
+        self._extract_data(data_path)
+        self._add_meta()
+        self._transform_data()
+
+    def _download_data(self, data_path: Path):
+        """Download source data from default location."""
+        logger.info('Downloading ChemIDplus data...')
+        outfile_path = data_path / self._src_fname
+        try:
+            with FTP(self._src_server) as ftp:
+                ftp.login()
+                logger.debug('FTP login successful.')
+                ftp.cwd(self._src_dir_path)
+                with open(outfile_path, 'wb') as fp:
+                    ftp.retrbinary(f'RETR {self._src_fname}', fp.write)
+            logger.info('Downloaded ChemIDplus source file.')
+        except TimeoutError:
+            logger.error('Connection to EBI FTP server timed out.')
+        date = ET.parse(outfile_path).getroot().attrib['date']
+        version = date.replace('-', '')
+        outfile_path.rename(data_path / f'chemidplus_{version}.xml')
+        logger.info('Finished downloading ChemIDplus data')
+
+    def _extract_data(self, data_path: Path):
+        """Acquire ChemIDplus dataset.
+
+        :arg pathlib.Path data_path: directory containing source data
+        """
+        data_path.mkdir(exist_ok=True, parents=True)
+        dir_files = list(data_path.iterdir())
+
+        if len(dir_files) == 0:
+            file = self._get_file(data_path)
+        else:
+            file = sorted([f for f in dir_files
+                           if f.name.startswith('chemidplus')])
+            if not file:
+                file = self._get_file(data_path)
+        self._data_src = file[-1]
+        self._version = self._data_src.stem.split('_')[1]
+
+    def _get_file(self, data_dir):
+        self._download_data()
+        dir_files = list(data_dir.iterdir())
+        return sorted([f for f in dir_files
+                       if f.names.startswith('chemidplus')])
+
+    def _transform_data(self):
+        """Open dataset and prepare for loading into database."""
+        tree = ET.parse(self._data_src)
+        root = tree.getroot()
+        with self.database.therapies.batch_writer() as batch:
+            for chemical in root:
+                if 'displayName' not in chemical.attrib:
+                    continue
+
+                # initial setup and get label
+                display_name = chemical.attrib['displayName']
+                if not display_name or not re.search(TAGS_REGEX, display_name):
+                    continue
+                label = re.sub(TAGS_REGEX, '', display_name)
+                params = {
+                    'label': label
+                }
+
+                # get concept ID
+                reg_no = chemical.find('NumberList').find("CASRegistryNumber")
+                if not reg_no:
+                    continue
+                params['concept_id'] = f'{NamespacePrefix.CASREGISTRY.value}:{reg_no.text}'  # noqa: E501
+
+                # get aliases
+                aliases = []
+                label_l = label.lower()
+                name_list = chemical.find('NameList')
+                if name_list:
+                    for name in name_list.findall('NameOfSubstance'):
+                        text = name.text
+                        if text != display_name and text.lower() != label_l:
+                            aliases.append(re.sub(TAGS_REGEX, '', text))
+                params['aliases'] = aliases
+
+                # get other_ids and xrefs
+                params['other_identifiers'] = list()
+                params['xrefs'] = list()
+                locator_list = chemical.find('LocatorList')
+                if locator_list:
+                    for loc in locator_list.findall('InternetLocator'):
+                        if loc.text == 'DrugBank':
+                            db = f'{NamespacePrefix.DRUGBANK.value}:{loc.attrib["url"].split("/")[-1]}'  # noqa: E501
+                            params['other_identifiers'].append(db)
+                        elif loc.text == 'FDA SRS':
+                            fda = f'{NamespacePrefix.FDA.value}:{loc.attrib["url"].split("/")[-1]}'  # noqa: E501
+                            params['xrefs'].append(fda)
+
+                # double-check and load full record
+                assert Drug(**params)
+                self._load_record(batch, params)
+
+    def _load_record(self, batch: BatchWriter, record: Dict):
+        """Load individual record into database.
+
+        :param boto3.dynamodb.table.BatchWriter batch: dynamodb batch writer
+            instance
+        :param therapy.schemas.Drug record: complete drug record to upload
+        """
+        concept_id_l = record['concept_id'].lower()
+        for alias in {a.lower() for a in record['aliases']}:
+            batch.put_item(Item={
+                'label_and_type': f'{alias}##alias',
+                'concept_id': concept_id_l,
+                'src_name': SourceName.CHEMIDPLUS.value
+            })
+        if 'label' in record:
+            batch.put_item(Item={
+                'label_and_type': f'{record["label"].lower()}##label',
+                'concept_id': concept_id_l,
+                'src_name': SourceName.CHEMIDPLUS.value
+            })
+        record['src_name'] = SourceName.CHEMIDPLUS.value
+        record['label_and_type'] = f'{concept_id_l}##identity'
+        batch.put_item(Item=record)
+        self._added_ids.add(record['concept_id'])
+
+    def _add_meta(self):
+        """Add source metadata."""
+        meta = Meta(data_license="custom",
+                    data_license_url="https://www.nlm.nih.gov/databases/download/terms_and_conditions.html",  # noqa: E501
+                    version=self._version,
+                    data_url="ftp://ftp.nlm.nih.gov/nlmdata/.chemidlease/",
+                    rdp_url=None,
+                    data_license_attributes=DataLicenseAttributes(
+                        non_commercial=False,
+                        share_alike=False,
+                        attribution=False
+                    ))
+        item = dict(meta)
+        item['src_name'] = SourceName.CHEMIDPLUS.value
+        self.database.metadata.put_item(Item=item)
diff --git a/therapy/etl/drugbank.py b/therapy/etl/drugbank.py
index 92b1817b..7690af6c 100644
--- a/therapy/etl/drugbank.py
+++ b/therapy/etl/drugbank.py
@@ -175,8 +175,8 @@ def _load_approval_status(self, element, params):
     def _load_cas_number(self, element, params):
         """Load cas number as other identifiers."""
         if element.text:
-            params['xrefs'].append(
-                f"{IDENTIFIER_PREFIXES['casRegistry']}:"
+            params['other_identifiers'].append(
+                f"{IDENTIFIER_PREFIXES['ChemIDplus']}:"
                 f"{element.text}")
 
     def _load_external_identifiers(self, element, params, xmlns,
diff --git a/therapy/etl/ncit.py b/therapy/etl/ncit.py
index 01419626..f5a38c39 100644
--- a/therapy/etl/ncit.py
+++ b/therapy/etl/ncit.py
@@ -154,12 +154,13 @@ def _transform_data(self, *args, **kwargs):
                     aliases.remove(label)
 
                 xrefs = []
+                other_ids = []
                 if node.P207:
                     xrefs.append(f"{NamespacePrefix.UMLS.value}:"
                                  f"{node.P207.first()}")
                 if node.P210:
-                    xrefs.append(f"{NamespacePrefix.CASREGISTRY.value}:"
-                                 f"{node.P210.first()}")
+                    other_ids.append(f"{NamespacePrefix.CASREGISTRY.value}:"
+                                     f"{node.P210.first()}")
                 if node.P319:
                     xrefs.append(f"{NamespacePrefix.FDA.value}:"
                                  f"{node.P319.first()}")
@@ -174,7 +175,7 @@ def _transform_data(self, *args, **kwargs):
                                   src_name=SourceName.NCIT.value,
                                   label=label,
                                   aliases=aliases,
-                                  other_identifiers=[],
+                                  other_identifiers=other_ids,
                                   xrefs=xrefs)
                 self._load_therapy(therapy, batch)
 
@@ -227,7 +228,7 @@ def _load_therapy(self, therapy: Therapy, batch):
             del therapy['label']
         item['label_and_type'] = f"{concept_id_lower}##identity"
         item['src_name'] = SourceName.NCIT.value
-        del item['other_identifiers']
-        if not item['xrefs']:
-            del item['xrefs']
+        for element in ['other_identifiers', 'xrefs']:
+            if not item[element]:
+                del item[element]
         batch.put_item(Item=item)
diff --git a/therapy/etl/rxnorm.py b/therapy/etl/rxnorm.py
new file mode 100644
index 00000000..69aab132
--- /dev/null
+++ b/therapy/etl/rxnorm.py
@@ -0,0 +1,455 @@
+"""This module defines the RxNorm ETL methods.
+
+"This product uses publicly available data courtesy of 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."
+
+ "This material includes SNOMED Clinical Terms® (SNOMED CT®) which is used by
+ permission of the International Health Terminology Standards Development
+ Organisation (IHTSDO). All rights reserved. SNOMED CT®, was originally
+ created by The College of American Pathologists. "SNOMED" and "SNOMED CT"
+ are registered trademarks of the IHTSDO."
+"""
+from .base import Base
+from therapy import PROJECT_ROOT, DownloadException
+from therapy.database import Database
+from therapy.schemas import SourceName, NamespacePrefix, Meta, Drug, \
+    ApprovalStatus
+import csv
+import datetime
+import logging
+import botocore
+from os import environ, remove
+import subprocess
+import shutil
+import zipfile
+import re
+import yaml
+
+logger = logging.getLogger('therapy')
+logger.setLevel(logging.DEBUG)
+
+# Designated Alias, Designated Syn, Tall Man Syn, Machine permutation
+# Generic Drug Name, Designated Preferred Name, Preferred Entry Term,
+# Clinical Drug, Entry Term, Rxnorm Preferred
+ALIASES = ['SYN', 'SY', 'TMSY', 'PM',
+           'GN', 'PT', 'PEP', 'CD', 'ET', 'RXN_PT']
+
+# Fully-specified drug brand name that can be prescribed
+# Fully-specified drug brand name that can not be prescribed,
+# Semantic branded drug
+TRADE_NAMES = ['BD', 'BN', 'SBD']
+
+# Allowed xrefs
+XREFS = ['ATC', 'CVX', 'DRUGBANK', 'MMSL', 'MSH', 'MTHCMSFRF', 'MTHSPL',
+         'RXNORM', 'SNOMEDCT_US', 'USP', 'VANDF']
+
+
+class RxNorm(Base):
+    """Extract, transform, and load the RxNorm source."""
+
+    def __init__(self,
+                 database: Database,
+                 data_url='https://www.nlm.nih.gov/research/umls/'
+                          'rxnorm/docs/rxnormfiles.html'):
+        """Initialize the RxNorm ETL class.
+
+        :param Database database: Access to DynamoDB.
+        :param str data_url: URL to RxNorm data files.
+        """
+        self._other_id_srcs = {src for src in SourceName.__members__}
+        self._xref_srcs = {src for src in NamespacePrefix.__members__}
+        self._data_url = data_url
+        self.database = database
+        self._load_data()
+
+    def _extract_data(self, *args, **kwargs):
+        """Extract data from the RxNorm source."""
+        logger.info('Extracting RxNorm...')
+        if 'data_path' in kwargs:
+            self._data_src = kwargs['data_path']
+        else:
+            rxn_dir = PROJECT_ROOT / 'data' / 'rxnorm'
+            rxn_dir.mkdir(exist_ok=True, parents=True)
+            rxn_files = list(rxn_dir.iterdir())
+            if len(rxn_files) == 0:
+                self._download_data(rxn_dir)
+
+            # file might already exist
+            files = sorted([fn for fn in rxn_dir.iterdir() if fn.name.
+                           startswith('rxnorm_') and fn.name.endswith('.RRF')],
+                           reverse=True)
+            file_found = False
+            for file in files:
+                version = str(file).split('_')[-1].split('.')[0]
+                try:
+                    datetime.datetime.strptime(version, '%Y%m%d')
+                    self._data_src = file
+                    self._version = version
+                    file_found = True
+                    break
+                except ValueError:
+                    pass
+            if not file_found:
+                self._download_data(rxn_dir)
+            self._create_drug_form_yaml(rxn_dir)
+        logger.info('Successfully extracted RxNorm.')
+
+    def _download_data(self, rxn_dir):
+        """Download RxNorm data file.
+
+        :param Path rxn_dir: Path to RxNorm data directory.
+        """
+        logger.info('Downloading RxNorm...')
+        if 'RXNORM_API_KEY' in environ.keys():
+            uri = 'https://download.nlm.nih.gov/umls/kss/' \
+                  'rxnorm/RxNorm_full_current.zip'
+
+            # Source:
+            # https://documentation.uts.nlm.nih.gov/automating-downloads.html
+            subprocess.call(['bash', f'{PROJECT_ROOT}/therapy/etl/'
+                                     f'rxnorm_download.sh', uri])
+
+            zip_path = rxn_dir / 'RxNorm_full_current.zip'
+            shutil.move(PROJECT_ROOT / 'RxNorm_full_current.zip', zip_path)
+
+            with zipfile.ZipFile(zip_path, 'r') as zf:
+                zf.extractall(rxn_dir)
+
+            remove(zip_path)
+            shutil.rmtree(rxn_dir / 'prescribe')
+            shutil.rmtree(rxn_dir / 'scripts')
+
+            # get version
+            readme = sorted([fn for fn in rxn_dir.iterdir() if fn.name.
+                            startswith('Readme')])[0]
+            version = str(readme).split('.')[0].split('_')[-1]
+            self._version = datetime.datetime.strptime(
+                version, '%m%d%Y').strftime('%Y%m%d')
+            remove(readme)
+
+            temp_file = rxn_dir / 'rrf' / 'RXNCONSO.RRF'
+            self._data_src = rxn_dir / f"rxnorm_{self._version}.RRF"
+            shutil.move(temp_file, self._data_src)
+            shutil.rmtree(rxn_dir / 'rrf')
+        else:
+            logger.error('Could not find RXNORM_API_KEY in environment '
+                         'variables.')
+            raise DownloadException("RXNORM_API_KEY not found.")
+
+    def _create_drug_form_yaml(self, rxn_dir):
+        """Create a YAML file containing RxNorm drug form values.
+
+        :param Path rxn_dir: Path to RxNorm data directory.
+        """
+        self._drug_forms_file = rxn_dir / 'drug_forms.yaml'
+        if not self._drug_forms_file.exists():
+            dfs = list()
+            with open(self._data_src) as f:
+                data = csv.reader(f, delimiter='|')
+                for row in data:
+                    if row[12] == 'DF' and row[11] == 'RXNORM':
+                        if row[14] not in dfs:
+                            dfs.append(row[14])
+
+            with open(self._drug_forms_file, 'w') as file:
+                yaml.dump(dfs, file)
+
+    def _transform_data(self):
+        """Transform the RxNorm source."""
+        with open(self._drug_forms_file, 'r') as file:
+            drug_forms = yaml.safe_load(file)
+
+        with open(self._data_src) as f:
+            rff_data = csv.reader(f, delimiter='|')
+            ingredient_brands = dict()  # Link ingredient to brand
+            precise_ingredient = dict()  # Link precise ingredient to get brand
+            data = dict()  # Transformed therapy records
+            sbdfs = dict()  # Link ingredient to brand
+            for row in rff_data:
+                if row[11] in XREFS:
+                    concept_id = f"{NamespacePrefix.RXNORM.value}:{row[0]}"
+                    if row[12] == 'SBDC':
+                        # Semantic Branded Drug Component
+                        self._get_brands(row, ingredient_brands)
+                    else:
+                        if concept_id not in data.keys():
+                            params = dict()
+                            params['concept_id'] = concept_id
+                            self._add_str_field(params, row,
+                                                precise_ingredient,
+                                                drug_forms, sbdfs)
+                            self._add_other_ids_xrefs(params, row)
+                            data[concept_id] = params
+                        else:
+                            # Concept already created
+                            params = data[concept_id]
+                            self._add_str_field(params, row,
+                                                precise_ingredient,
+                                                drug_forms, sbdfs)
+                            self._add_other_ids_xrefs(params, row)
+
+            with self.database.therapies.batch_writer() as batch:
+                for key, value in data.items():
+                    if 'label' in value:
+                        self._get_trade_names(value, precise_ingredient,
+                                              ingredient_brands, sbdfs)
+                        params = Drug(
+                            concept_id=value['concept_id'],
+                            label=value['label'] if 'label' in value else None,
+                            approval_status=value[
+                                'approval_status'] if 'approval_status'
+                                                      in value else None,
+                            aliases=value['aliases'] if 'aliases' in
+                                                        value else [],
+                            other_identifiers=value[
+                                'other_identifiers'] if 'other_identifiers'
+                                                        in value else [],
+                            xrefs=value['xrefs'] if 'xrefs' in value else [],
+                            trade_names=value['trade_names'] if 'trade_names'
+                                                                in value else []  # noqa: E501
+                        )
+                        self._load_therapy(params, batch)
+
+    def _load_therapy(self, params, batch):
+        """Load therapy record into the database.
+
+        :param Drug params: A Therapy object.
+        :param BatchWriter batch: Object to write data to DynamoDB.
+        """
+        params = dict(params)
+        for label_type in ['label', 'aliases', 'other_identifiers', 'xrefs',
+                           'trade_names']:
+            if not params[label_type]:
+                del params[label_type]
+
+        for attr in ['trade_names', 'aliases']:
+            # Remove duplicates
+            if attr in params:
+                if 'label' in params:
+                    params[attr] = list(set(params[attr]) - {params['label']})
+
+                if attr == 'aliases' and 'trade_names' in params:
+                    params[attr] = \
+                        list(set(params[attr]) - set(params['trade_names']))
+
+                # 20 cutoff for aliases / trade names
+                if len(params[attr]) > 20:
+                    logger.debug(f"{params['concept_id']} has more than 20"
+                                 f" {attr}.")
+                    del params[attr]
+
+        self._load_label_types(params, batch)
+        params['src_name'] = SourceName.RXNORM.value
+        params['label_and_type'] = f"{params['concept_id'].lower()}##identity"
+        try:
+            batch.put_item(Item=params)
+        except botocore.exceptions.ClientError:
+            if (len((params['label_and_type']).encode('utf-8')) >= 2048) or \
+                    (len((params['concept_id']).encode('utf-8')) >= 1024):
+                logger.info(f"{params['concept_id']}: An error occurred "
+                            "(ValidationException) when calling the "
+                            "BatchWriteItem operation: Hash primary key "
+                            "values must be under 2048 bytes, and range"
+                            " primary key values must be under 1024"
+                            " bytes.")
+
+    def _load_label_types(self, params, batch):
+        """Insert aliases, trade_names, and label data into the database.
+
+        :param dict params: A transformed therapy record.
+        :param BatchWriter batch: Object to write data to DynamoDB
+        """
+        if 'label' in params:
+            self._load_label_type(params, batch, 'label', 'label')
+        if 'trade_names' in params:
+            self._load_label_type(params, batch, 'trade_name', 'trade_names')
+        if 'aliases' in params:
+            self._load_label_type(params, batch, 'alias', 'aliases')
+
+    def _load_label_type(self, params, batch, label_type_sing, label_type_pl):
+        """Insert alias, trade_name, or label data into the database.
+
+        :param dict params: A transformed therapy record.
+        :param BatchWriter batch: Object to write data to DynamoDB
+        :param str label_type_sing: The singular label type
+        :param str label_type_pl: The plural label type
+        """
+        if isinstance(params[label_type_pl], list):
+            terms = {t.casefold(): t for t in params[label_type_pl]}.values()
+        else:
+            terms = [params[label_type_pl]]
+        for t in terms:
+            t = {
+                'label_and_type': f"{t.lower()}##{label_type_sing}",
+                'concept_id': f"{params['concept_id'].lower()}",
+                'src_name': SourceName.RXNORM.value
+            }
+            try:
+                batch.put_item(Item=t)
+            except botocore.exceptions.ClientError:
+                if (len((t['label_and_type']).encode('utf-8')) >= 2048) or \
+                        (len((t['concept_id']).encode('utf-8')) >= 1024):
+                    logger.info(f"{params['concept_id']}: An error occurred "
+                                "(ValidationException) when calling the "
+                                "BatchWriteItem operation: Hash primary key "
+                                "values must be under 2048 bytes, and range"
+                                " primary key values must be under 1024"
+                                " bytes.")
+        return terms
+
+    def _get_brands(self, row, ingredient_brands):
+        """Add ingredient and brand to ingredient_brands.
+
+        :param list row: A row in the RxNorm data file.
+        :param dict ingredient_brands: Store brands for each ingredient
+        """
+        # SBDC: Ingredient(s) + Strength + [Brand Name]
+        term = row[14]
+        ingredients_brand = \
+            re.sub(r"(\d*)(\d*\.)?\d+ (MG|UNT|ML)?(/(ML|HR|MG))?",
+                   "", term)
+        brand = term.split('[')[-1].split(']')[0]
+        ingredients = ingredients_brand.replace(f"[{brand}]", '')
+        if '/' in ingredients:
+            ingredients = ingredients.split('/')
+            for ingredient in ingredients:
+                self._add_term(ingredient_brands, brand,
+                               ingredient.strip())
+        else:
+            self._add_term(ingredient_brands, brand,
+                           ingredients.strip())
+
+    def _get_trade_names(self, value, precise_ingredient, ingredient_brands,
+                         sbdfs):
+        """Get trade names for a given ingredient.
+
+        :param dict value: Therapy attributes
+        :param dict precise_ingredient: Brand names for precise ingredient
+        :param dict ingredient_brands: Brand names for ingredient
+        :param dict sbdfs: Brand names for ingredient from SBDF row
+        """
+        record_label = value['label'].lower()
+        labels = [record_label]
+
+        if 'PIN' in value and value['PIN'] \
+                in precise_ingredient:
+            for pin in precise_ingredient[value['PIN']]:
+                labels.append(pin.lower())
+
+        for label in labels:
+            trade_names = \
+                [val for key, val in ingredient_brands.items()
+                 if label == key.lower()]
+            trade_names = {val for sublist in trade_names
+                           for val in sublist}
+            for tn in trade_names:
+                self._add_term(value, tn, 'trade_names')
+
+        if record_label in sbdfs:
+            for tn in sbdfs[record_label]:
+                self._add_term(value, tn, 'trade_names')
+
+    def _add_str_field(self, params, row, precise_ingredient, drug_forms,
+                       sbdfs):
+        """Differentiate STR field.
+
+        :param dict params: A transformed therapy record.
+        :param list row: A row in the RxNorm data file.
+        :param dict precise_ingredient: Precise ingredient information
+        :param list drug_forms: RxNorm Drug Form values
+        :param dict precise_ingredient: Brand names for precise ingredient
+        """
+        term = row[14]
+        term_type = row[12]
+        source = row[11]
+
+        if term_type == 'IN' and source == 'RXNORM':
+            params['label'] = term
+            if row[17] == '4096':
+                params['approval_status'] = ApprovalStatus.APPROVED.value
+        elif term_type in ALIASES:
+            if term_type == 'PT' and source == 'SNOMEDCT_US':
+                if 'containing' not in term:
+                    # Dont want to store aliases that have "-containing ..."
+                    self._add_term(params, term, 'aliases')
+            else:
+                self._add_term(params, term, 'aliases')
+        elif term_type in TRADE_NAMES:
+            self._add_term(params, term, 'trade_names')
+
+        if source == 'RXNORM':
+            if term_type == 'SBDF':
+                brand = term.split('[')[-1].split(']')[0]
+                ingredient_strength = term.replace(f"[{brand}]", '')
+                for df in drug_forms:
+                    if df in ingredient_strength:
+                        ingredient = \
+                            ingredient_strength.replace(df, '').strip()
+                        self._add_term(sbdfs, brand, ingredient.lower())
+                        break
+
+        if source == 'MSH':
+            if term_type == 'MH':
+                # Get ID for accessing precise ingredient
+                params['PIN'] = row[13]
+            elif term_type == 'PEP':
+                self._add_term(precise_ingredient, term, row[13])
+
+    def _add_term(self, params, term, label_type):
+        """Add a single term to a therapy record in an associated field.
+
+        :param dict params: A transformed therapy record.
+        :param str term: The term to be added
+        :param str label_type: The type of term
+        """
+        if label_type in params and params[label_type]:
+            if term not in params[label_type]:
+                params[label_type].append(term)
+        else:
+            params[label_type] = [term]
+
+    def _add_other_ids_xrefs(self, params, row):
+        """Add other identifier or xref to therapy.
+
+        :param dict params: A transformed therapy record.
+        :param list row: A row in the RxNorm data file.
+        """
+        if row[11]:
+            other_id_xref = row[11].upper()
+            if other_id_xref in self._other_id_srcs:
+                source_id =\
+                    f"{NamespacePrefix[other_id_xref].value}:{row[13]}"
+                if source_id != params['concept_id']:
+                    # Sometimes concept_id is included in the source field
+                    self._add_term(params, source_id, 'other_identifiers')
+            elif other_id_xref in self._xref_srcs:
+                source_id = f"{NamespacePrefix[other_id_xref].value}:{row[13]}"
+                self._add_term(params, source_id, 'xrefs')
+            else:
+                logger.info(f"{other_id_xref} not in NameSpacePrefix.")
+
+    def _add_meta(self):
+        """Add RxNorm metadata."""
+        meta = Meta(data_license='UMLS Metathesaurus',
+                    data_license_url='https://www.nlm.nih.gov/research/umls/'
+                                     'rxnorm/docs/termsofservice.html',
+                    version=self._version,
+                    data_url=self._data_url,
+                    rdp_url=None,
+                    data_license_attributes={
+                        'non_commercial': False,
+                        'share_alike': False,
+                        'attribution': False
+                    })
+        params = dict(meta)
+        params['src_name'] = SourceName.RXNORM.value
+        params['snomedct_version'] = '20200731'
+        self.database.metadata.put_item(Item=params)
+
+    def _load_data(self):
+        """Load the RxNorm source into database."""
+        self._extract_data()
+        self._add_meta()
+        self._transform_data()
diff --git a/therapy/etl/rxnorm_download.sh b/therapy/etl/rxnorm_download.sh
new file mode 100644
index 00000000..0c8ab35f
--- /dev/null
+++ b/therapy/etl/rxnorm_download.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+# Source: https://documentation.uts.nlm.nih.gov/automating-downloads.html
+#1. Add your API key from https://uts.nlm.nih.gov/uts/profile
+#2. Run the script (for example: bash curl-uts-downloads-apikey.sh https://download.nlm.nih.gov/umls/kss/rxnorm/RxNorm_weekly_current.zip)
+
+export apikey=$RXNORM_API_KEY
+export DOWNLOAD_URL=$1
+
+export CAS_LOGIN_URL=https://utslogin.nlm.nih.gov/cas/v1/api-key
+
+if [ -z "$apikey" ]; then echo " Please enter you api key "
+   exit
+fi
+
+if [ $# -eq 0 ]; then echo "Usage: curl-uts-downloads-apikey.sh  download_url "
+                      echo "  e.g.   curl-uts-download-apikey.sh https://download.nlm.nih.gov/umls/kss/rxnorm/RxNorm_full_current.zip"
+                      echo "         curl-uts-download-apikey.sh https://download.nlm.nih.gov/umls/kss/rxnorm/RxNorm_weekly_current.zip"
+   exit
+fi
+
+TGT=$(curl -d "apikey="$apikey -H "Content-Type: application/x-www-form-urlencoded" -X POST https://utslogin.nlm.nih.gov/cas/v1/api-key)
+
+TGTTICKET=$(echo $TGT | tr "=" "\n")
+
+for TICKET in $TGTTICKET
+do
+    if [[ "$TICKET" == *"TGT"* ]]; then
+	  SUBSTRING=$(echo $TICKET| cut -d'/' -f 7)
+	  TGTVALUE=$(echo $SUBSTRING | sed 's/.$//')
+	fi
+done
+echo $TGTVALUE
+STTICKET=$(curl -d "service="$DOWNLOAD_URL -H "Content-Type: application/x-www-form-urlencoded" -X POST https://utslogin.nlm.nih.gov/cas/v1/tickets/$TGTVALUE)
+echo $STTICKET
+
+curl -c cookie.txt -b cookie.txt -L -O -J $DOWNLOAD_URL?ticket=$STTICKET
+rm cookie.txt
\ No newline at end of file
diff --git a/therapy/etl/wikidata.py b/therapy/etl/wikidata.py
index 129e0427..19408b25 100644
--- a/therapy/etl/wikidata.py
+++ b/therapy/etl/wikidata.py
@@ -111,7 +111,9 @@ def _transform_data(self):
                         if key in record.keys():
                             other_id = record[key]
 
-                            # get other_ids
+                            if key.upper() == 'CASREGISTRY':
+                                key = SourceName.CHEMIDPLUS.value
+
                             if key.upper() in normalizer_srcs:
                                 if key != 'chembl':
                                     fmted_other_id = \
diff --git a/therapy/schemas.py b/therapy/schemas.py
index 11c62347..31e1a527 100644
--- a/therapy/schemas.py
+++ b/therapy/schemas.py
@@ -11,8 +11,8 @@ class Therapy(BaseModel):
 
     label: str
     concept_id: str
-    aliases: List[str]
-    other_identifiers: List[str]
+    aliases: Optional[List[str]]
+    other_identifiers: Optional[List[str]]
     xrefs: Optional[List[str]]
 
     class Config:
@@ -116,6 +116,8 @@ class SourceName(Enum):
     CHEMBL = "ChEMBL"
     NCIT = "NCIt"
     DRUGBANK = "DrugBank"
+    CHEMIDPLUS = "ChemIDplus"
+    RXNORM = "RxNorm"
 
 
 class SourceIDAfterNamespace(Enum):
@@ -125,12 +127,15 @@ class SourceIDAfterNamespace(Enum):
     CHEMBL = "CHEMBL"
     DRUGBANK = "DB"
     NCIT = "C"
+    CHEMIDPLUS = ""
+    RXNORM = ""
 
 
 class NamespacePrefix(Enum):
     """Define string constraints for namespace prefixes on concept IDs."""
 
-    CASREGISTRY = "chemidplus"
+    CHEMIDPLUS = "chemidplus"
+    CASREGISTRY = CHEMIDPLUS
     PUBCHEMCOMPOUND = "pubchem.compound"
     PUBCHEMSUBSTANCE = "pubchem.substance"
     CHEMBL = "chembl"
@@ -152,6 +157,15 @@ class NamespacePrefix(Enum):
     THERAPEUTICTARGETSDB = "ttd"
     IUPHAR = "iuphar"
     GUIDETOPHARMACOLOGY = "gtopdb"
+    ATC = "atc"  # Anatomical Therapeutic Chemical Classification System
+    CVX = "cvx"  # Vaccines Administered
+    MMSL = "mmsl"  # Multum MediSource Lexicon
+    MSH = "msh"  # Medical Subject Headings
+    MTHCMSFRF = "mthcmsfrf"  # CMS Formulary Reference File
+    MTHSPL = "mthspl"  # FDA Structured Product Labels
+    SNOMEDCT_US = "snomedct"  # US Edition of SNOMED CT
+    USP = "usp"  # USP Compendial Nomenclature
+    VANDF = "vandf"  # Veterans Health Administration National Drug File
 
 
 class DataLicenseAttributes(BaseModel):


Test patch:
diff --git a/tests/unit/data/metadata.json b/tests/unit/data/metadata.json
index 45b75f60..4643ef9b 100644
--- a/tests/unit/data/metadata.json
+++ b/tests/unit/data/metadata.json
@@ -17,8 +17,6 @@
     "data_license": "CC0 1.0",
     "data_license_url": "https://creativecommons.org/publicdomain/zero/1.0/",
     "version": "20200812",
-    "data_url": null,
-    "rdp_url": null,
     "data_license_attributes": {
       "non_commercial": false,
       "share_alike": false,
@@ -50,5 +48,30 @@
       "share_alike": false,
       "attribution": true
     }
+  },
+  {
+    "src_name": "ChemIDplus",
+    "data_license": "custom",
+    "data_license_url": "https://www.nlm.nih.gov/databases/download/terms_and_conditions.html",
+    "version": "20200327",
+    "data_url": "ftp://ftp.nlm.nih.gov/nlmdata/.chemidlease/",
+    "rdp_url": null,
+    "data_license_attributes": {
+      "non_commercial": false,
+      "share_alike": false,
+      "attribution": false
+    }
+  },
+  {
+    "src_name": "RxNorm",
+    "data_license": "UMLS Metathesaurus",
+    "data_license_url": "https://www.nlm.nih.gov/research/umls/rxnorm/docs/termsofservice.html",
+    "version": "20210104",
+    "data_url": "https://www.nlm.nih.gov/research/umls/rxnorm/docs/rxnormfiles.html",
+    "data_license_attributes": {
+      "non_commercial": false,
+      "share_alike": false,
+      "attribution": false
+    }
   }
 ]
diff --git a/tests/unit/data/therapies.json b/tests/unit/data/therapies.json
index 0739d21e..44076e64 100644
--- a/tests/unit/data/therapies.json
+++ b/tests/unit/data/therapies.json
@@ -4,23 +4,19 @@
     "concept_id": "chembl:CHEMBL11359",
     "label": "CISPLATIN",
     "aliases": [
-        "Cisplatin",
-        "Cis-Platinum II",
-        "Cisplatinum",
-        "cis-diamminedichloroplatinum(II)",
-        "CIS-DDP",
-        "INT-230-6 COMPONENT CISPLATIN",
-        "INT230-6 COMPONENT CISPLATIN",
-        "NSC-119875",
-        "Platinol",
-        "Platinol-Aq"
+      "Cisplatin",
+      "Cis-Platinum II",
+      "Cisplatinum",
+      "cis-diamminedichloroplatinum(II)",
+      "CIS-DDP",
+      "INT-230-6 COMPONENT CISPLATIN",
+      "INT230-6 COMPONENT CISPLATIN",
+      "NSC-119875",
+      "Platinol",
+      "Platinol-Aq"
     ],
     "approval_status": "approved",
-    "trade_names": [
-        "PLATINOL",
-        "PLATINOL-AQ",
-        "CISPLATIN"
-    ],
+    "trade_names": ["PLATINOL", "PLATINOL-AQ", "CISPLATIN"],
     "src_name": "ChEMBL"
   },
   {
@@ -84,22 +80,23 @@
     "label": "Cisplatin",
     "concept_id": "drugbank:DB00515",
     "aliases": [
-        "CDDP",
-        "Cis-DDP",
-        "cis-diamminedichloroplatinum(II)",
-        "Cisplatin",
-        "APRD00359",
-        "Abiplatin",
-        "Cisplatyl",
-        "Platidiam",
-        "Platin"
+      "CDDP",
+      "Cis-DDP",
+      "cis-diamminedichloroplatinum(II)",
+      "Cisplatin",
+      "APRD00359",
+      "Abiplatin",
+      "Cisplatyl",
+      "Platidiam",
+      "Platin"
     ],
     "approval_status": "approved",
     "other_identifiers": [
-        "chembl:CHEMBL2068237"
+      "chemidplus:15663-27-1",
+      "chembl:CHEMBL2068237",
+      "rxcui:2555"
     ],
     "xrefs": [
-      "chemidplus:15663-27-1",
       "chebi:27899",
       "pubchem.compound:2767",
       "pubchem.substance:46504561",
@@ -109,22 +106,21 @@
       "bindingdb:50028111",
       "pharmgkb.drug:PA449014",
       "pdb:CPT",
-      "ttd:DAP000215",
-      "rxcui:2555"
+      "ttd:DAP000215"
     ],
     "trade_names": [
-        "CISplatin",
-        "Cisplatin",
-        "Cisplatin Inj 0.5mg/ml",
-        "Cisplatin Inj 1mg/ml",
-        "Cisplatin Injection",
-        "Cisplatin Injection BP",
-        "Cisplatin Injection, BP",
-        "Cisplatin Injection, Mylan Std.",
-        "Platinol",
-        "Platinol AQ Inj 1mg/ml",
-        "Platinol-AQ",
-        "PMS-cisplatin"
+      "CISplatin",
+      "Cisplatin",
+      "Cisplatin Inj 0.5mg/ml",
+      "Cisplatin Inj 1mg/ml",
+      "Cisplatin Injection",
+      "Cisplatin Injection BP",
+      "Cisplatin Injection, BP",
+      "Cisplatin Injection, Mylan Std.",
+      "Platinol",
+      "Platinol AQ Inj 1mg/ml",
+      "Platinol-AQ",
+      "PMS-cisplatin"
     ],
     "src_name": "DrugBank"
   },
@@ -167,19 +163,14 @@
     "label_and_type": "drugbank:db00522##identity",
     "label": "Bentiromide",
     "concept_id": "drugbank:DB00522",
-    "aliases": [
-        "APRD00818",
-        "Bentiromide",
-        "BTPABA",
-        "PFT",
-        "PFD"
-    ],
+    "aliases": ["APRD00818", "Bentiromide", "BTPABA", "PFT", "PFD"],
     "approval_status": "withdrawn",
     "other_identifiers": [
-        "chembl:CHEMBL1200368"
+      "chemidplus:37106-97-1",
+      "rxcui:18896",
+      "chembl:CHEMBL1200368"
     ],
     "xrefs": [
-      "chemidplus:37106-97-1",
       "chebi:31263",
       "pubchem.compound:6957673",
       "pubchem.substance:46508175",
@@ -187,8 +178,7 @@
       "chemspider:5329364",
       "bindingdb:50240073",
       "pharmgkb.drug:PA164750572",
-      "zinc:ZINC000000608204",
-      "rxcui:18896"
+      "zinc:ZINC000000608204"
     ],
     "src_name": "DrugBank"
   },
@@ -218,19 +208,17 @@
     "concept_id": "drugbank:DB14201",
     "approval_status": "approved",
     "other_identifiers": [
-        "chembl:CHEMBL508112"
+      "chemidplus:120-78-5",
+      "chembl:CHEMBL508112",
+      "rxcui:1306112"
     ],
     "xrefs": [
-      "chemidplus:120-78-5",
       "chemspider:8139",
       "bindingdb:50444458",
       "chebi:53239",
-      "zinc:ZINC000001555224",
-      "rxcui:1306112"
-    ],
-    "trade_names": [
-        "T.R.U.E. Test Thin-Layer Rapid Use Patch Test"
+      "zinc:ZINC000001555224"
     ],
+    "trade_names": ["T.R.U.E. Test Thin-Layer Rapid Use Patch Test"],
     "src_name": "DrugBank"
   },
   {
@@ -248,22 +236,20 @@
     "label": "cisplatin",
     "concept_id": "wikidata:Q412415",
     "aliases": [
-        "CDDP",
-        "Cis-DDP",
-        "CIS-DDP",
-        "cis-diamminedichloroplatinum(II)",
-        "Platinol",
-        "Platinol-AQ"
+      "CDDP",
+      "Cis-DDP",
+      "CIS-DDP",
+      "cis-diamminedichloroplatinum(II)",
+      "Platinol",
+      "Platinol-AQ"
     ],
     "other_identifiers": [
-        "chembl:CHEMBL11359",
-        "drugbank:DB00515"
-    ],
-    "xrefs": [
-      "rxcui:2555",
       "chemidplus:15663-27-1",
-      "pubchem.compound:5702198"
+      "chembl:CHEMBL11359",
+      "drugbank:DB00515",
+      "rxcui:2555"
     ],
+    "xrefs": ["pubchem.compound:5702198"],
     "src_name": "Wikidata"
   },
   {
@@ -281,24 +267,23 @@
     "label": "Interferon alfacon-1",
     "concept_id": "wikidata:Q15353101",
     "aliases": [
-        "CIFN",
-        "consensus interferon",
-        "IFN Alfacon-1",
-        "Interferon Consensus, Methionyl",
-        "methionyl interferon consensus",
-        "methionyl-interferon-consensus",
-        "rCon-IFN",
-        "Recombinant Consensus Interferon",
-        "Recombinant methionyl human consensus interferon"
+      "CIFN",
+      "consensus interferon",
+      "IFN Alfacon-1",
+      "Interferon Consensus, Methionyl",
+      "methionyl interferon consensus",
+      "methionyl-interferon-consensus",
+      "rCon-IFN",
+      "Recombinant Consensus Interferon",
+      "Recombinant methionyl human consensus interferon"
     ],
     "other_identifiers": [
-        "chembl:CHEMBL1201557",
-        "drugbank:DB00069"
-    ],
-    "xrefs": [
+      "chembl:CHEMBL1201557",
+      "drugbank:DB00069",
       "rxcui:59744",
       "chemidplus:118390-30-0"
     ],
+    "xrefs": [],
     "src_name": "Wikidata"
   },
   {
@@ -311,14 +296,12 @@
     "label": "D-methamphetamine",
     "concept_id": "wikidata:Q191924",
     "other_identifiers": [
-      "chembl:CHEMBL1201201",
-      "drugbank:DB01577"
-    ],
-    "xrefs": [
       "chemidplus:537-46-2",
-      "pubchem.compound:10836",
+      "chembl:CHEMBL1201201",
+      "drugbank:DB01577",
       "rxcui:6816"
     ],
+    "xrefs": ["pubchem.compound:10836"],
     "src_name": "Wikidata"
   },
   {
@@ -331,23 +314,22 @@
     "label": "atropine",
     "concept_id": "wikidata:Q26272",
     "aliases": [
-        "dl-Hyoscyamine",
-        "dl-tropyltropate",
-        "Mydriasine",
-        "Tropine tropate",
-        "(±)-atropine",
-        "(±)-hyoscyamine",
-        "(+-)-Atropine",
-        "(+-)-Hyoscyamine",
-        "(+,-)-Tropyl tropate",
-        "(3-Endo)-8-methyl-8-azabicyclo[3.2.1]oct-3-yl tropate",
-        "[(1S,5R)-8-Methyl-8-azabicyclo[3.2.1]oct-3-yl] 3-hydroxy-2-phenyl-propanoate",
-        "8-Methyl-8-azabicyclo[3.2.1]oct-3-yl 3-hydroxy-2-phenylpropanoate",
-        "8-Methyl-8-azabicyclo[3.2.1]oct-3-yl tropate"
-    ],
-    "xrefs": [
-        "rxcui:1223"
+      "dl-Hyoscyamine",
+      "dl-tropyltropate",
+      "Mydriasine",
+      "Tropine tropate",
+      "(±)-atropine",
+      "(±)-hyoscyamine",
+      "(+-)-Atropine",
+      "(+-)-Hyoscyamine",
+      "(+,-)-Tropyl tropate",
+      "(3-Endo)-8-methyl-8-azabicyclo[3.2.1]oct-3-yl tropate",
+      "[(1S,5R)-8-Methyl-8-azabicyclo[3.2.1]oct-3-yl] 3-hydroxy-2-phenyl-propanoate",
+      "8-Methyl-8-azabicyclo[3.2.1]oct-3-yl 3-hydroxy-2-phenylpropanoate",
+      "8-Methyl-8-azabicyclo[3.2.1]oct-3-yl tropate"
     ],
+    "xrefs": [],
+    "other_identifiers": ["rxcui:1223"],
     "src_name": "Wikidata"
   },
   {
@@ -369,11 +351,16 @@
     "label_and_type": "ncit:c95221##identity",
     "label": "Voglibose",
     "concept_id": "ncit:C95221",
-    "aliases": ["3,4-Dideoxy-4-((2-Hydroxy-1-(Hydroxymethyl)Ethyl)Amino)-2-C-(Hydroxymethyl)-D-Epi-Inositol",
-                "A-71100", "AO-128", "Basen",
-                "N-(1,3-Dihydroxy-2-Propyl)Valiolamine", "VOGLIBOSE"],
-    "xrefs": ["chemidplus:83480-29-9", "fda:S77P977AG8",
-                          "umls:C0532578"],
+    "aliases": [
+      "3,4-Dideoxy-4-((2-Hydroxy-1-(Hydroxymethyl)Ethyl)Amino)-2-C-(Hydroxymethyl)-D-Epi-Inositol",
+      "A-71100",
+      "AO-128",
+      "Basen",
+      "N-(1,3-Dihydroxy-2-Propyl)Valiolamine",
+      "VOGLIBOSE"
+    ],
+    "other_identifiers": ["chemidplus:83480-29-9"],
+    "xrefs": ["fda:S77P977AG8", "umls:C0532578"],
     "src_name": "NCIt"
   },
   {
@@ -395,10 +382,16 @@
     "label_and_type": "ncit:c74021##identity",
     "label": "Apricoxib",
     "concept_id": "ncit:C74021",
-    "aliases": ["APRICOXIB", "COX-2 Inhibitor TG01", "CS-706", "R-109339",
-                "TG01", "TP2001"],
-    "xrefs": ["chemidplus:197904-84-0", "fda:5X5HB3VZ3Z",
-                          "umls:C1737955"],
+    "aliases": [
+      "APRICOXIB",
+      "COX-2 Inhibitor TG01",
+      "CS-706",
+      "R-109339",
+      "TG01",
+      "TP2001"
+    ],
+    "other_identifiers": ["chemidplus:197904-84-0"],
+    "xrefs": ["fda:5X5HB3VZ3Z", "umls:C1737955"],
     "src_name": "NCIt"
   },
   {
@@ -410,8 +403,8 @@
     "label_and_type": "ncit:c1647##identity",
     "label": "Trastuzumab",
     "concept_id": "ncit:C1647",
-    "xrefs": ["umls:C0728747", "chemidplus:180288-69-1",
-                          "fda:P188ANX8CK"],
+    "other_identifiers": ["chemidplus:180288-69-1"],
+    "xrefs": ["umls:C0728747", "fda:P188ANX8CK"],
     "src_name": "NCIt"
   },
   {
@@ -423,16 +416,578 @@
     "label_and_type": "ncit:c376##identity",
     "label": "Cisplatin",
     "concept_id": "ncit:C376",
-    "xrefs": [
-      "umls:C0008838",
-      "chemidplus:15663-27-1",
-      "fda:Q20Q21Q62"
-    ],
+    "other_identifiers": ["chemidplus:15663-27-1"],
+    "xrefs": ["umls:C0008838", "fda:Q20Q21Q62"],
     "src_name": "NCIt"
   },
   {
     "label_and_type": "cisplatin##label",
     "concept_id": "ncit:c376",
     "src_name": "NCIt"
+  },
+  {
+    "label_and_type": "chemidplus:87-08-1##identity",
+    "concept_id": "chemidplus:87-08-1",
+    "label": "Penicillin V",
+    "aliases": ["Phenoxymethylpenicillin"],
+    "trade_names": [],
+    "other_identifiers": ["drugbank:DB00417"],
+    "xrefs": ["fda:Z61I075U2W"],
+    "src_name": "ChemIDplus"
+  },
+  {
+    "label_and_type": "penicillin v##label",
+    "concept_id": "chemidplus:87-08-1",
+    "src_name": "ChemIDplus"
+  },
+  {
+    "label_and_type": "phenoxymethylpenicillin##alias",
+    "concept_id": "chemidplus:87-08-1",
+    "src_name": "ChemIDplus"
+  },
+  {
+    "label_and_type": "chemidplus:152459-95-5##identity",
+    "concept_id": "chemidplus:152459-95-5",
+    "label": "Imatinib",
+    "aliases": [],
+    "trade_names": [],
+    "other_identifiers": ["drugbank:DB00619"],
+    "xrefs": ["fda:BKJ8M8G5HI"],
+    "src_name": "ChemIDplus"
+  },
+  {
+    "label_and_type": "imatinib##label",
+    "concept_id": "chemidplus:152459-95-5",
+    "src_name": "ChemIDplus"
+  },
+  {
+    "label_and_type": "chemidplus:15663-27-1##identity",
+    "concept_id": "chemidplus:15663-27-1",
+    "label": "Cisplatin",
+    "aliases": [
+      "cis-Diaminedichloroplatinum",
+      "1,2-Diaminocyclohexaneplatinum II citrate"
+    ],
+    "trade_names": [],
+    "other_identifiers": ["drugbank:DB00515"],
+    "xrefs": ["fda:Q20Q21Q62J"],
+    "src_name": "ChemIDplus"
+  },
+  {
+    "label_and_type": "cisplatin##label",
+    "concept_id": "chemidplus:15663-27-1",
+    "src_name": "ChemIDplus"
+  },
+  {
+    "label_and_type": "cis-diaminedichloroplatinum##alias",
+    "concept_id": "chemidplus:15663-27-1",
+    "src_name": "ChemIDplus"
+  },
+  {
+    "label_and_type": "1,2-diaminocyclohexaneplatinum ii citrate##alias",
+    "concept_id": "chemidplus:15663-27-1",
+    "src_name": "ChemIDplus"
+  },
+  {
+    "label_and_type": "rxcui:100213##identity",
+    "concept_id": "rxcui:100213",
+    "label": "Bifidobacterium Infantis",
+    "approval_status": "approved",
+    "aliases": [
+      "bifidobacterium infantis",
+      "Bifidobacterium infantis"
+    ],
+    "trade_names": [
+      "Align",
+      "Evivo"
+    ],
+    "other_identifiers": ["drugbank:DB14222"],
+    "xrefs": [
+      "mmsl:d07347"
+    ],
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "bifidobacterium infantis##label",
+    "concept_id": "rxcui:100213",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "align##trade_name",
+    "concept_id": "rxcui:100213",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "evivo##trade_name",
+    "concept_id": "rxcui:100213",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "rxcui:2555##identity",
+    "concept_id": "rxcui:2555",
+    "label": "cisplatin",
+    "approval_status": "approved",
+    "aliases": [
+      "Diaminedichloroplatinum",
+      "cis-Platinum",
+      "CISplatin",
+      "cis Diamminedichloroplatinum",
+      "cis Platinum",
+      "Diamminodichloride, Platinum",
+      "cis-Platinum compound",
+      "cis-DDP",
+      "cis-Diaminedichloroplatinum",
+      "cis-Platinum II",
+      "DDP",
+      "Dichlorodiammineplatinum",
+      "cis-Diamminedichloroplatinum",
+      "cis-Dichlorodiammineplatinum(II)",
+      "Platinum Diamminodichloride",
+      "cis-Diamminedichloroplatinum(II)",
+      "cis-Platinum",
+      "cis-diamminedichloroplatinum(II)",
+      "CDDP",
+      "Cis-DDP"
+    ],
+    "trade_names": [
+      "Cisplatin",
+      "Platinol"
+    ],
+    "other_identifiers": [
+      "drugbank:DB00515",
+      "drugbank:DB12117"
+    ],
+    "xrefs": [
+      "usp:m17910",
+      "snomedct:57066004",
+      "vandf:4018139",
+      "msh:D002945",
+      "mthspl:Q20Q21Q62J",
+      "mmsl:d00195",
+      "atc:L01XA01",
+      "mmsl:31747",
+      "mmsl:4456",
+      "snomedct:387318005"
+    ],
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "cisplatin##label",
+    "concept_id": "rxcui:2555",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "cis-ddp##alias",
+    "concept_id": "rxcui:2555",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "diaminedichloroplatinum##alias",
+    "concept_id": "rxcui:2555",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "cis platinum##alias",
+    "concept_id": "rxcui:2555",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "platinol##trade_name",
+    "concept_id": "rxcui:2555",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "rxcui:644##identity",
+    "concept_id": "rxcui:644",
+    "label": "amiloride",
+    "approval_status": "approved",
+    "aliases": [
+      "3,5-diamino-N-carbamimidoyl-6-chloropyrazine-2-carboxamide",
+      "Amiloride",
+      "aMILoride",
+      "Amipramidin",
+      "Amipramidine",
+      "N-amidino-3,5-diamino-6-chloropyrazinecarboxamide",
+      "Amyloride"
+    ],
+    "trade_names": [
+      "Midamor",
+      "Frusemek",
+      "Moduret",
+      "Aridil",
+      "Zida-Co",
+      "Amilamont",
+      "Moduretic"
+    ],
+    "other_identifiers": [
+      "drugbank:DB00594"
+    ],
+    "xrefs": [
+      "snomedct:387503008",
+      "msh:D000584",
+      "vandf:4019603",
+      "mthspl:7DZO8EB0Z3",
+      "mmsl:d00169",
+      "atc:C03DB01",
+      "snomedct:87395005"
+    ],
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "amiloride##label",
+    "concept_id": "rxcui:644",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "amyloride##alias",
+    "concept_id": "rxcui:644",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "3,5-diamino-n-carbamimidoyl-6-chloropyrazine-2-carboxamide##alias",
+    "concept_id": "rxcui:644",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "midamor##trade_name",
+    "concept_id": "rxcui:644",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "rxcui:10600##identity",
+    "concept_id": "rxcui:10600",
+    "label": "timolol",
+    "approval_status": "approved",
+    "aliases": [
+      "(S)-1-((1,1-Dimethylethyl)amino)-3-((4-(4-morpholinyl)-1,2,5-thiadazol-3-yl)oxy)-2-propanol",
+      "2-Propanol, 1-((1,1-dimethylethyl)amino)-3-((4-(4-morpholinyl)-1,2,5-thiadiazol-3-yl)oxy)-, (S)-",
+      "Timolol",
+      "(S)-1-(tert-butylamino)-3-[(4-morpholin-4-yl-1,2,5-thiadiazol-3-yl)oxy]propan-2-ol"
+    ],
+    "trade_names": [
+      "Betimol",
+      "Timoptic",
+      "Istalol",
+      "Combigan",
+      "Cosopt",
+      "Timoptol LA",
+      "Blocadren",
+      "Timolide",
+      "Betim",
+      "Glau-Opt",
+      "Glaucol",
+      "Timoptol"
+    ],
+    "other_identifiers": [
+      "drugbank:DB00373"
+    ],
+    "xrefs": [
+      "vandf:4019949",
+      "mthspl:817W3C6175",
+      "snomedct:372880004",
+      "msh:D013999",
+      "mmsl:d00139",
+      "atc:C07AA06",
+      "atc:S01ED01",
+      "snomedct:85591001"
+    ],
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "timolol##label",
+    "concept_id": "rxcui:10600",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "(s)-1-(tert-butylamino)-3-[(4-morpholin-4-yl-1,2,5-thiadiazol-3-yl)oxy]propan-2-ol##alias",
+    "concept_id": "rxcui:10600",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "betimol##trade_name",
+    "concept_id": "rxcui:10600",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "rxcui:1011##identity",
+    "concept_id": "rxcui:1011",
+    "label": "lymphocyte immune globulin, anti-thymocyte globulin",
+    "approval_status": "approved",
+    "aliases": [
+      "Antithymocyte immunoglobulin",
+      "Lymphocyte Immune Globulin, Anti-Thymocyte Globulin",
+      "Anti-Thymocyte Globulin",
+      "Antithymocyte Globulin",
+      "Antithymoglobulin",
+      "Lymphocyte Immune Globulin, Anti Thymocyte Globulin",
+      "Globulins, Antithymocyte",
+      "Antithymocyte Globulins",
+      "Antithymoglobulins",
+      "Anti-Thymocyte Globulins",
+      "Globulins, Anti-Thymocyte",
+      "Globulin, Antithymocyte",
+      "Anti Thymocyte Globulin",
+      "Globulin, Anti-Thymocyte",
+      "lymphocyte immune globulin, anti-thy (obs)"
+    ],
+    "trade_names": [
+      "ATGAM",
+      "Thymoglobulin"
+    ],
+    "xrefs": [
+      "snomedct:768651008",
+      "msh:D000961",
+      "vandf:4022194",
+      "vandf:4018097",
+      "mmsl:d01141",
+      "mmsl:5001"
+    ],
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "lymphocyte immune globulin, anti-thymocyte globulin##label",
+    "concept_id": "rxcui:1011",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "anti thymocyte globulin##alias",
+    "concept_id": "rxcui:1011",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "antithymoglobulin##alias",
+    "concept_id": "rxcui:1011",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "thymoglobulin##trade_name",
+    "concept_id": "rxcui:1011",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "atgam##trade_name",
+    "concept_id": "rxcui:1011",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "rxcui:1191##identity",
+    "concept_id": "rxcui:1191",
+    "label": "aspirin",
+    "approval_status": "approved",
+    "aliases": [
+      "Salicylic acid acetate",
+      "Aspirin",
+      "Acetylsalicylic acid",
+      "Acid, Acetylsalicylic",
+      "2-(Acetyloxy)benzoic Acid",
+      "2-Acetoxybenzenecarboxylic acid",
+      "ASA",
+      "O-acetylsalicylic acid",
+      "2-Acetoxybenzoic acid",
+      "o-acetoxybenzoic acid",
+      "o-carboxyphenyl acetate",
+      "Acetylsalicylic Acid"
+    ],
+    "other_identifiers": [
+      "drugbank:DB00945"
+    ],
+    "xrefs": [
+      "usp:m6240",
+      "snomedct:7947003",
+      "vandf:4017536",
+      "mmsl:34512",
+      "mthspl:R16CO5Y76E",
+      "mmsl:244",
+      "snomedct:387458008",
+      "msh:D001241",
+      "mmsl:4223",
+      "mmsl:d00170",
+      "atc:A01AD05",
+      "atc:B01AC06",
+      "atc:N02BA01",
+      "snomedct:7947003"
+    ],
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "aspirin##label",
+    "concept_id": "rxcui:1191",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "acetylsalicylic acid##alias",
+    "concept_id": "rxcui:1191",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "asa##alias",
+    "concept_id": "rxcui:1191",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "rxcui:44##identity",
+    "concept_id": "rxcui:44",
+    "label": "mesna",
+    "approval_status": "approved",
+    "aliases": [
+      "Sodium 2-mercaptoethanesulfonate",
+      "Mesnum",
+      "Ethanesulfonic acid, 2-mercapto-, monosodium salt",
+      "Sodium 2-Mercaptoethanesulphonate",
+      "2-Mercaptoethanesulphonate, Sodium",
+      "Sodium 2-Mercaptoethanesulphonate"
+    ],
+    "trade_names": [
+      "Mesna",
+      "Mesnex",
+      "Mesna Nova Plus"
+    ],
+    "xrefs": [
+      "usp:m49500",
+      "snomedct:108821000",
+      "snomedct:386922000",
+      "msh:D015080",
+      "vandf:4019477",
+      "mthspl:NR7O1405Q9",
+      "atc:R05CB05",
+      "atc:V03AF01",
+      "mmsl:41498",
+      "snomedct:386922000",
+      "mmsl:5057",
+      "mmsl:d01411"
+    ],
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "mesna##label",
+    "concept_id": "rxcui:44",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "mesnum##alias",
+    "concept_id": "rxcui:44",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "ethanesulfonic acid, 2-mercapto-, monosodium salt##alias",
+    "concept_id": "rxcui:44",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "mesnex##trade_name",
+    "concept_id": "rxcui:44",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "rxcui:61##identity",
+    "concept_id": "rxcui:61",
+    "label": "beta-alanine",
+    "approval_status": "approved",
+    "aliases": [
+      "Beta alanine",
+      "beta Alanine",
+      "3 Aminopropionic Acid",
+      "3-Aminopropionic Acid"
+    ],
+    "xrefs": [
+      "snomedct:70587000",
+      "msh:D015091",
+      "vandf:4028377",
+      "mthspl:11P2JDE17B"
+    ],
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "beta-alanine##label",
+    "concept_id": "rxcui:61",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "3 aminopropionic acid##alias",
+    "concept_id": "rxcui:61",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "rxcui:595##identity",
+    "concept_id": "rxcui:595",
+    "label": "algestone",
+    "approval_status": null,
+    "aliases": [
+      "Pregn-4-ene-3,20-dione, 16,17-dihydroxy-, (16alpha)-",
+      "16 alpha,17-Dihydroxypregn-4-ene-3,20-dione",
+      "Alphasone",
+      "Dihydroxyprogesterone"
+    ],
+    "xrefs": [
+      "msh:D000523"
+    ],
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "algestone##label",
+    "concept_id": "rxcui:595",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "pregn-4-ene-3,20-dione, 16,17-dihydroxy-, (16alpha)-##alias",
+    "concept_id": "rxcui:595",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "rxcui:10582##identity",
+    "concept_id": "rxcui:10582",
+    "label": "levothyroxine",
+    "approval_status": "approved",
+    "trade_names": [
+      "Eltroxin",
+     "Estre",
+     "Euthyrox",
+     "Leventa",
+     "Levo-T",
+     "Levocrine",
+     "Levotabs",
+     "Levothroid",
+     "Levothyroid",
+     "Levoxyl",
+     "Novothyrox",
+     "SOLOXINE",
+     "Synthroid",
+     "Thyro-Tabs",
+     "ThyroKare",
+     "ThyroMed",
+     "Thyrolar",
+     "Thyrox",
+     "Tirosint",
+     "Unithroid"
+    ],
+    "other_identifiers": [
+      "drugbank:DB00451"
+    ],
+    "xrefs": [
+      "snomedct:768532006",
+      "snomedct:73187006",
+      "vandf:4022126",
+      "msh:D013974",
+      "mmsl:d00278",
+      "mthspl:Q51BO43MG4",
+      "snomedct:710809001",
+      "snomedct:38076006"
+    ],
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "levothyroxine##label",
+    "concept_id": "rxcui:10582",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "unithroid##trade_name",
+    "concept_id": "rxcui:10582",
+    "src_name": "RxNorm"
+  },
+  {
+    "label_and_type": "euthyrox##trade_name",
+    "concept_id": "rxcui:10582",
+    "src_name": "RxNorm"
   }
-]
\ No newline at end of file
+]
diff --git a/tests/unit/test_chemidplus_source.py b/tests/unit/test_chemidplus_source.py
new file mode 100644
index 00000000..b9cc9d04
--- /dev/null
+++ b/tests/unit/test_chemidplus_source.py
@@ -0,0 +1,158 @@
+"""Test ChemIDplus ETL methods."""
+from therapy.query import Normalizer
+from therapy.schemas import Drug, MatchType
+import pytest
+from typing import Dict
+
+
+@pytest.fixture(scope='module')
+def chemidplus():
+    """Build ETL test fixture."""
+    class QueryGetter:
+
+        def __init__(self):
+            self.normalizer = Normalizer()
+
+        def normalize(self, query_str):
+            resp = self.normalizer.normalize(query_str, keyed=True,
+                                             incl='chemidplus')
+            return resp['source_matches']['ChemIDplus']
+    return QueryGetter()
+
+
+@pytest.fixture(scope='module')
+def penicillin_v():
+    """Build test fixture for Penicillin V drug."""
+    return Drug(**{
+        "concept_id": "chemidplus:87-08-1",
+        "label": "Penicillin V",
+        "aliases": [
+            'Phenoxymethylpenicillin'
+        ],
+        "trade_names": [],
+        "other_identifiers": [
+            "drugbank:DB00417",
+        ],
+        "xrefs": [
+            "fda:Z61I075U2W",
+        ]
+    })
+
+
+@pytest.fixture(scope='module')
+def imatinib():
+    """Build test fixture for Imatinib."""
+    return Drug(**{
+        "concept_id": "chemidplus:152459-95-5",
+        "label": "Imatinib",
+        "aliases": [],
+        "trade_names": [],
+        "other_identifiers": [
+            "drugbank:DB00619",
+        ],
+        "xrefs": [
+            "fda:BKJ8M8G5HI",
+        ]
+    })
+
+
+@pytest.fixture(scope='module')
+def cisplatin():
+    """Build test fixture for cisplatin."""
+    return Drug(**{
+        'concept_id': 'chemidplus:15663-27-1',
+        'label': 'Cisplatin',
+        'aliases': [
+            'cis-Diaminedichloroplatinum',
+            '1,2-Diaminocyclohexaneplatinum II citrate'
+        ],
+        'trade_names': [],
+        'other_identifiers': [
+            'drugbank:DB00515'
+        ],
+        "xrefs": [
+            'fda:Q20Q21Q62J'
+        ]
+    })
+
+
+def compare_records(actual_record: Dict, fixture_record: Drug):
+    """Check that records are identical."""
+    assert actual_record.concept_id == fixture_record.concept_id
+    assert actual_record.label == fixture_record.label
+    assert set(actual_record.trade_names) == set(fixture_record.trade_names)
+    assert set(actual_record.other_identifiers) == \
+        set(fixture_record.other_identifiers)
+    assert set(actual_record.xrefs) == set(fixture_record.xrefs)
+    assert actual_record.approval_status == fixture_record.approval_status
+
+
+def test_concept_id_match(chemidplus, penicillin_v):
+    """Test that records are retrieved by concept ID correctly."""
+    response = chemidplus.normalize('chemidplus:87-08-1')
+    assert response['match_type'] == MatchType.CONCEPT_ID
+    assert len(response['records']) == 1
+    compare_records(response['records'][0], penicillin_v)
+
+    response = chemidplus.normalize('CHemidplus:87-08-1')
+    assert response['match_type'] == MatchType.CONCEPT_ID
+    assert len(response['records']) == 1
+    compare_records(response['records'][0], penicillin_v)
+
+    response = chemidplus.normalize('87-08-1')
+    assert response['match_type'] == MatchType.NO_MATCH
+
+    response = chemidplus.normalize('87081')
+    assert response['match_type'] == MatchType.NO_MATCH
+
+
+def test_label_match(chemidplus, imatinib, penicillin_v):
+    """Test that records are retrieved by label correctly."""
+    response = chemidplus.normalize('Penicillin V')
+    assert response['match_type'] == MatchType.LABEL
+    assert len(response['records']) == 1
+    compare_records(response['records'][0], penicillin_v)
+
+    response = chemidplus.normalize('Imatinib')
+    assert response['match_type'] == MatchType.LABEL
+    assert len(response['records']) == 1
+    compare_records(response['records'][0], imatinib)
+
+    response = chemidplus.normalize('imatiniB')
+    assert response['match_type'] == MatchType.LABEL
+    assert len(response['records']) == 1
+    compare_records(response['records'][0], imatinib)
+
+    response = chemidplus.normalize('PenicillinV')
+    assert response['match_type'] == MatchType.NO_MATCH
+
+
+def test_alias_match(chemidplus, penicillin_v, cisplatin):
+    """Test that records are retrieved by alias correctly."""
+    response = chemidplus.normalize('cis-Diaminedichloroplatinum')
+    assert response['match_type'] == MatchType.ALIAS
+    assert len(response['records']) == 1
+    compare_records(response['records'][0], cisplatin)
+
+    response = chemidplus.normalize('Phenoxymethylpenicillin')
+    assert response['match_type'] == MatchType.ALIAS
+    assert len(response['records']) == 1
+    compare_records(response['records'][0], penicillin_v)
+
+    response = chemidplus.normalize('Cisplatine')
+    assert response['match_type'] == MatchType.NO_MATCH
+
+
+def test_meta(chemidplus):
+    """Test correctness of source metadata."""
+    response = chemidplus.normalize('incoherent-string-of-text')
+    assert response['meta_'].data_license == 'custom'
+    assert response['meta_'].data_license_url == 'https://www.nlm.nih.gov/databases/download/terms_and_conditions.html'  # noqa: E501
+    assert response['meta_'].version == '20200327'
+    assert response['meta_'].data_url == 'ftp://ftp.nlm.nih.gov/nlmdata/.chemidlease/'  # noqa: E501
+    assert response['meta_'].rdp_url is None
+    assert response['meta_'].data_license_attributes == {
+        "non_commercial": False,
+        "share_alike": False,
+        "attribution": False
+    }
diff --git a/tests/unit/test_drugbank_source.py b/tests/unit/test_drugbank_source.py
index 727e6723..263a323d 100644
--- a/tests/unit/test_drugbank_source.py
+++ b/tests/unit/test_drugbank_source.py
@@ -45,10 +45,11 @@ def cisplatin():
         ],
         'approval_status': 'approved',
         'other_identifiers': [
-            'chembl:CHEMBL2068237'
+            'rxcui:2555',
+            'chembl:CHEMBL2068237',
+            'chemidplus:15663-27-1',
         ],
         'xrefs': [
-            'chemidplus:15663-27-1',
             'chebi:27899',
             'pubchem.compound:2767',
             'pubchem.substance:46504561',
@@ -59,7 +60,6 @@ def cisplatin():
             'pharmgkb.drug:PA449014',
             'pdb:CPT',
             'ttd:DAP000215',
-            'rxcui:2555'
         ],
         'trade_names': [
             'CISplatin',
@@ -94,10 +94,11 @@ def bentiromide():
         ],
         'approval_status': 'withdrawn',
         'other_identifiers': [
+            'rxcui:18896',
+            'chemidplus:37106-97-1',
             'chembl:CHEMBL1200368'
         ],
         'xrefs': [
-            'chemidplus:37106-97-1',
             'chebi:31263',
             'pubchem.compound:6957673',
             'pubchem.substance:46508175',
@@ -106,7 +107,6 @@ def bentiromide():
             'bindingdb:50240073',
             'pharmgkb.drug:PA164750572',
             'zinc:ZINC000000608204',
-            'rxcui:18896'
         ],
         'trade_names': []
     }
@@ -123,15 +123,15 @@ def db14201():
         'aliases': [],
         'approval_status': 'approved',
         'other_identifiers': [
+            'rxcui:1306112',
+            'chemidplus:120-78-5',
             'chembl:CHEMBL508112'
         ],
         'xrefs': [
-            'chemidplus:120-78-5',
             'chemspider:8139',
             'bindingdb:50444458',
             'chebi:53239',
             'zinc:ZINC000001555224',
-            'rxcui:1306112'
         ],
         'trade_names': [
             'T.R.U.E. Test Thin-Layer Rapid Use Patch Test'
@@ -617,7 +617,7 @@ def test_no_match(drugbank):
     assert len(normalizer_response['records']) == 0
 
 
-def test_meta_info(cisplatin, drugbank):
+def test_meta_info(drugbank):
     """Test that the meta field is correct."""
     normalizer_response = drugbank.fetch_meta()
     assert normalizer_response.data_license == 'CC BY-NC 4.0'
diff --git a/tests/unit/test_ncit_source.py b/tests/unit/test_ncit_source.py
index fb6c087f..76804365 100644
--- a/tests/unit/test_ncit_source.py
+++ b/tests/unit/test_ncit_source.py
@@ -28,8 +28,8 @@ def voglibose():
         'aliases': ['3,4-Dideoxy-4-((2-Hydroxy-1-(Hydroxymethyl)Ethyl)Amino)-2-C-(Hydroxymethyl)-D-Epi-Inositol',  # noqa F401
                     'A-71100', 'AO-128', 'Basen',
                     'N-(1,3-Dihydroxy-2-Propyl)Valiolamine', 'VOGLIBOSE'],
-        'other_identifiers': [],
-        'xrefs': ['chemidplus:83480-29-9', 'fda:S77P977AG8', 'umls:C0532578'],
+        'other_identifiers': ['chemidplus:83480-29-9'],
+        'xrefs': ['fda:S77P977AG8', 'umls:C0532578'],
         'approval_status': None,
         'trade_names': []
     }
@@ -44,8 +44,8 @@ def apricoxib():
         'concept_id': 'ncit:C74021',
         'aliases': ['APRICOXIB', 'COX-2 Inhibitor TG01', 'CS-706', 'R-109339',
                     'TG01', 'TP2001'],
-        'other_identifiers': [],
-        'xrefs': ['chemidplus:197904-84-0', 'fda:5X5HB3VZ3Z', 'umls:C1737955'],
+        'other_identifiers': ['chemidplus:197904-84-0'],
+        'xrefs': ['fda:5X5HB3VZ3Z', 'umls:C1737955'],
         'approval_status': None,
         'trade_names': []
     }
@@ -60,8 +60,8 @@ def trastuzumab():
         'label': 'Trastuzumab',
         'concept_id': 'ncit:C1647',
         'aliases': [],
-        'other_identifiers': [],
-        'xrefs': ['umls:C0728747', 'chemidplus:180288-69-1', 'fda:P188ANX8CK'],
+        'other_identifiers': ['chemidplus:180288-69-1'],
+        'xrefs': ['umls:C0728747', 'fda:P188ANX8CK'],
         'approval_status': None,
         'trade_names': []
     }
diff --git a/tests/unit/test_rxnorm_source.py b/tests/unit/test_rxnorm_source.py
new file mode 100644
index 00000000..ba1e84b1
--- /dev/null
+++ b/tests/unit/test_rxnorm_source.py
@@ -0,0 +1,1138 @@
+"""Test that the therapy normalizer works as intended for the RxNorm source."""
+import pytest
+from therapy.schemas import Drug, MatchType
+from therapy.query import Normalizer
+
+
+@pytest.fixture(scope='module')
+def rxnorm():
+    """Build RxNorm normalizer test fixture."""
+    class QueryGetter:
+
+        def __init__(self):
+            self.normalizer = Normalizer()
+
+        def normalize(self, query_str):
+            resp = self.normalizer.normalize(query_str, keyed=True,
+                                             incl='rxnorm')
+            return resp['source_matches']['RxNorm']
+
+        def fetch_meta(self):
+            return self.normalizer.fetch_meta('RxNorm')
+
+    return QueryGetter()
+
+
+@pytest.fixture(scope='module')
+def bifidobacterium_infantis():
+    """Create bifidobacterium infantis drug fixture."""
+    params = {
+        'label': 'Bifidobacterium Infantis',
+        'concept_id': 'rxcui:100213',
+        'aliases': [
+            'bifidobacterium infantis',
+            'Bifidobacterium infantis'
+        ],
+        'approval_status': 'approved',
+        'other_identifiers': [
+            'drugbank:DB14222'
+        ],
+        'xrefs': [
+            'mmsl:d07347'
+        ],
+        'trade_names': [
+            'Align',
+            'Evivo'
+        ]
+    }
+    return Drug(**params)
+
+
+@pytest.fixture(scope='module')
+def cisplatin():
+    """Create cisplatin drug fixture."""
+    params = {
+        'label': 'cisplatin',
+        'concept_id': 'rxcui:2555',
+        'aliases': [
+            'Diaminedichloroplatinum',
+            'cis-Platinum',
+            'CISplatin',
+            'cis Diamminedichloroplatinum',
+            'cis Platinum',
+            'Diamminodichloride, Platinum',
+            'cis-Platinum compound',
+            'cis-DDP',
+            'cis-Diaminedichloroplatinum',
+            'cis-Platinum II',
+            'DDP',
+            'Dichlorodiammineplatinum',
+            'cis-Diamminedichloroplatinum',
+            'cis-Dichlorodiammineplatinum(II)',
+            'Platinum Diamminodichloride',
+            'cis-Diamminedichloroplatinum(II)',
+            'cis-Platinum',
+            'cis-diamminedichloroplatinum(II)',
+            'CDDP',
+            'Cis-DDP',
+        ],
+        'approval_status': 'approved',
+        'other_identifiers': [
+            'drugbank:DB00515',
+            'drugbank:DB12117'
+        ],
+        'xrefs': [
+            'usp:m17910',
+            'snomedct:57066004',
+            'vandf:4018139',
+            'msh:D002945',
+            'mthspl:Q20Q21Q62J',
+            'mmsl:d00195',
+            'atc:L01XA01',
+            'mmsl:31747',
+            'mmsl:4456',
+            'snomedct:387318005'
+        ],
+        'trade_names': [
+            'Cisplatin',
+            'Platinol'
+        ]
+    }
+    return Drug(**params)
+
+
+@pytest.fixture(scope='module')
+def amiloride():
+    """Create amiloride hydrochloride, anhydrous drug fixture."""
+    params = {
+        'label': 'amiloride',
+        'concept_id': 'rxcui:644',
+        'aliases': [
+            '3,5-diamino-N-carbamimidoyl-6-chloropyrazine-2-carboxamide',
+            'Amiloride',
+            'aMILoride',
+            'Amipramidin',
+            'Amipramidine',
+            'N-amidino-3,5-diamino-6-chloropyrazinecarboxamide',
+            'Amyloride'
+        ],
+        'approval_status': 'approved',
+        'other_identifiers': [
+            'drugbank:DB00594'
+        ],
+        'xrefs': [
+            'snomedct:387503008',
+            'msh:D000584',
+            'vandf:4019603',
+            'mthspl:7DZO8EB0Z3',
+            'mmsl:d00169',
+            'atc:C03DB01',
+            'snomedct:87395005'
+        ],
+        'trade_names': [
+            'Midamor',
+            'Frusemek',
+            'Moduret',
+            'Aridil',
+            'Zida-Co',
+            'Amilamont',
+            'Moduretic'
+        ]
+    }
+    return Drug(**params)
+
+
+@pytest.fixture(scope='module')
+def timolol():
+    """Create timolol drug fixture."""
+    params = {
+        'label': 'timolol',
+        'concept_id': 'rxcui:10600',
+        'aliases': [
+            '(S)-1-((1,1-Dimethylethyl)amino)-3-((4-(4-morpholinyl)'
+            '-1,2,5-thiadazol-3-yl)oxy)-2-propanol',
+            '2-Propanol, 1-((1,1-dimethylethyl)amino)-3-((4-(4-morpholi'
+            'nyl)-1,2,5-thiadiazol-3-yl)oxy)-, (S)-',
+            'Timolol',
+            '(S)-1-(tert-butylamino)-3-[(4-morpholin-4-yl-1,2,5-thiadiazol'
+            '-3-yl)oxy]propan-2-ol'
+        ],
+        'approval_status': 'approved',
+        'other_identifiers': [
+            'drugbank:DB00373'
+        ],
+        'xrefs': [
+            'vandf:4019949',
+            'mthspl:817W3C6175',
+            'snomedct:372880004',
+            'msh:D013999',
+            'mmsl:d00139',
+            'atc:C07AA06',
+            'atc:S01ED01',
+            'snomedct:85591001'
+        ],
+        'trade_names': [
+            'Betimol',
+            'Timoptic',
+            'Istalol',
+            'Combigan',
+            'Cosopt',
+            'Timoptol LA',
+            'Blocadren',
+            'Timolide',
+            'Betim',
+            'Glau-Opt',
+            'Glaucol',
+            'Timoptol'
+        ]
+    }
+    return Drug(**params)
+
+
+@pytest.fixture(scope='module')
+def lymphocyte():
+    """Create lymphocyte immune globulin, anti-thymocyte globulin drug
+    fixture.
+    """
+    params = {
+        'label': 'lymphocyte immune globulin, anti-thymocyte globulin',
+        'concept_id': 'rxcui:1011',
+        'aliases': [
+            'Antithymocyte immunoglobulin',
+            'Lymphocyte Immune Globulin, Anti-Thymocyte Globulin',
+            'Anti-Thymocyte Globulin',
+            'Antithymocyte Globulin',
+            'Antithymoglobulin',
+            'Lymphocyte Immune Globulin, Anti Thymocyte Globulin',
+            'Globulins, Antithymocyte',
+            'Antithymocyte Globulins',
+            'Antithymoglobulins',
+            'Anti-Thymocyte Globulins',
+            'Globulins, Anti-Thymocyte',
+            'Globulin, Antithymocyte',
+            'Anti Thymocyte Globulin',
+            'Globulin, Anti-Thymocyte',
+            'lymphocyte immune globulin, anti-thy (obs)'
+        ],
+        'approval_status': 'approved',
+        'other_identifiers': [],
+        'xrefs': [
+            'snomedct:768651008',
+            'msh:D000961',
+            'vandf:4022194',
+            'vandf:4018097',
+            'mmsl:d01141',
+            'mmsl:5001'
+        ],
+        'trade_names': [
+            'ATGAM',
+            'Thymoglobulin'
+        ]
+    }
+    return Drug(**params)
+
+
+@pytest.fixture(scope='module')
+def aspirin():
+    """Create aspirin drug fixture."""
+    params = {
+        'label': 'aspirin',
+        'concept_id': 'rxcui:1191',
+        'aliases': [
+            'Salicylic acid acetate',
+            'Aspirin',
+            'Acetylsalicylic acid',
+            'Acid, Acetylsalicylic',
+            '2-(Acetyloxy)benzoic Acid',
+            '2-Acetoxybenzenecarboxylic acid',
+            'ASA',
+            'O-acetylsalicylic acid',
+            '2-Acetoxybenzoic acid',
+            'o-acetoxybenzoic acid',
+            'o-carboxyphenyl acetate',
+            'Acetylsalicylic Acid'
+        ],
+        'approval_status': 'approved',
+        'other_identifiers': [
+            'drugbank:DB00945'
+        ],
+        'xrefs': [
+            'usp:m6240',
+            'snomedct:7947003',
+            'vandf:4017536',
+            'mmsl:34512',
+            'mthspl:R16CO5Y76E',
+            'mmsl:244',
+            'snomedct:387458008',
+            'msh:D001241',
+            'mmsl:4223',
+            'mmsl:d00170',
+            'atc:A01AD05',
+            'atc:B01AC06',
+            'atc:N02BA01',
+            'snomedct:7947003'
+        ],
+        'trade_names': []
+    }
+    return Drug(**params)
+
+
+@pytest.fixture(scope='module')
+def mesna():
+    """Create mesna drug fixture."""
+    params = {
+        'label': 'mesna',
+        'concept_id': 'rxcui:44',
+        'aliases': [
+            'Sodium 2-mercaptoethanesulfonate',
+            'Mesnum',
+            'Ethanesulfonic acid, 2-mercapto-, monosodium salt',
+            'Sodium 2-Mercaptoethanesulphonate',
+            '2-Mercaptoethanesulphonate, Sodium',
+            'Sodium 2-Mercaptoethanesulphonate'
+        ],
+        'approval_status': 'approved',
+        'other_identifiers': [],
+        'xrefs': [
+            'usp:m49500',
+            'snomedct:108821000',
+            'snomedct:386922000',
+            'msh:D015080',
+            'vandf:4019477',
+            'mthspl:NR7O1405Q9',
+            'atc:R05CB05',
+            'atc:V03AF01',
+            'mmsl:41498',
+            'snomedct:386922000',
+            'mmsl:5057',
+            'mmsl:d01411'
+        ],
+        'trade_names': [
+            'Mesna',
+            'Mesnex',
+            'Mesna Nova Plus'
+        ]
+    }
+    return Drug(**params)
+
+
+@pytest.fixture(scope='module')
+def beta_alanine():
+    """Create beta-alanine drug fixture."""
+    params = {
+        'label': 'beta-alanine',
+        'concept_id': 'rxcui:61',
+        'aliases': [
+            'Beta alanine',
+            'beta Alanine',
+            '3 Aminopropionic Acid',
+            '3-Aminopropionic Acid'
+        ],
+        'approval_status': 'approved',
+        'other_identifiers': [],
+        'xrefs': [
+            'snomedct:70587000',
+            'msh:D015091',
+            'vandf:4028377',
+            'mthspl:11P2JDE17B'
+        ],
+        'trade_names': []
+    }
+    return Drug(**params)
+
+
+@pytest.fixture(scope='module')
+def algestone():
+    """Create algestone drug fixture."""
+    params = {
+        'label': 'algestone',
+        'concept_id': 'rxcui:595',
+        'aliases': [
+            'Pregn-4-ene-3,20-dione, 16,17-dihydroxy-, (16alpha)-',
+            '16 alpha,17-Dihydroxypregn-4-ene-3,20-dione',
+            'Alphasone',
+            'Dihydroxyprogesterone'
+        ],
+        'approval_status': None,
+        'other_identifiers': [],
+        'xrefs': [
+            'msh:D000523'
+        ],
+        'trade_names': []
+    }
+    return Drug(**params)
+
+
+@pytest.fixture(scope='module')
+def levothyroxine():
+    """Create levothyroxine drug fixture."""
+    params = {
+        'label': 'levothyroxine',
+        'concept_id': 'rxcui:10582',
+        'aliases': [],
+        'approval_status': 'approved',
+        'other_identifiers': [
+            'drugbank:DB00451'
+        ],
+        'xrefs': [
+            'snomedct:768532006',
+            'snomedct:73187006',
+            'vandf:4022126',
+            'msh:D013974',
+            'mmsl:d00278',
+            'mthspl:Q51BO43MG4',
+            'snomedct:710809001',
+            'snomedct:38076006'
+        ],
+        'trade_names': [
+            'Eltroxin',
+            'Estre',
+            'Euthyrox',
+            'Leventa',
+            'Levo-T',
+            'Levocrine',
+            'Levotabs',
+            'Levothroid',
+            'Levothyroid',
+            'Levoxyl',
+            'Novothyrox',
+            'SOLOXINE',
+            'Synthroid',
+            'Thyro-Tabs',
+            'ThyroKare',
+            'ThyroMed',
+            'Thyrolar',
+            'Thyrox',
+            'Tirosint',
+            'Unithroid'
+        ]
+    }
+    return Drug(**params)
+
+
+def test_bifidobacterium_infantis(bifidobacterium_infantis, rxnorm):
+    """Test that bifidobacterium_ nfantis drug normalizes to
+    correct drug concept.
+    """
+    # Concept ID Match
+    normalizer_response = rxnorm.normalize('RxCUI:100213')
+    assert normalizer_response['match_type'] == MatchType.CONCEPT_ID
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == bifidobacterium_infantis.label
+    assert normalized_drug.concept_id == bifidobacterium_infantis.concept_id
+    assert set(normalized_drug.aliases) == \
+           set(bifidobacterium_infantis.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(bifidobacterium_infantis.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(bifidobacterium_infantis.xrefs)
+    assert set(normalized_drug.trade_names) == \
+           set(bifidobacterium_infantis.trade_names)
+    assert normalized_drug.approval_status == \
+           bifidobacterium_infantis.approval_status
+
+    # Label Match
+    normalizer_response = rxnorm.normalize(' Bifidobacterium infantis')
+    assert normalizer_response['match_type'] == MatchType.LABEL
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == bifidobacterium_infantis.label
+    assert normalized_drug.concept_id == bifidobacterium_infantis.concept_id
+    assert set(normalized_drug.aliases) == \
+           set(bifidobacterium_infantis.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(bifidobacterium_infantis.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(bifidobacterium_infantis.xrefs)
+    assert set(normalized_drug.trade_names) == \
+           set(bifidobacterium_infantis.trade_names)
+    assert normalized_drug.approval_status == \
+           bifidobacterium_infantis.approval_status
+
+    normalizer_response = rxnorm.normalize('bifidobacterium infantis')
+    assert normalizer_response['match_type'] == MatchType.LABEL
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == bifidobacterium_infantis.label
+    assert normalized_drug.concept_id == bifidobacterium_infantis.concept_id
+    assert set(normalized_drug.aliases) == \
+           set(bifidobacterium_infantis.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(bifidobacterium_infantis.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(bifidobacterium_infantis.xrefs)
+    assert set(normalized_drug.trade_names) == \
+           set(bifidobacterium_infantis.trade_names)
+    assert normalized_drug.approval_status == \
+           bifidobacterium_infantis.approval_status
+
+    # Trade Name Match
+    normalizer_response = rxnorm.normalize('ALIGN')
+    assert normalizer_response['match_type'] == MatchType.TRADE_NAME
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == bifidobacterium_infantis.label
+    assert normalized_drug.concept_id == bifidobacterium_infantis.concept_id
+    assert set(normalized_drug.aliases) == \
+           set(bifidobacterium_infantis.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(bifidobacterium_infantis.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(bifidobacterium_infantis.xrefs)
+    assert set(normalized_drug.trade_names) == \
+           set(bifidobacterium_infantis.trade_names)
+    assert normalized_drug.approval_status == \
+           bifidobacterium_infantis.approval_status
+
+    normalizer_response = rxnorm.normalize('evivo')
+    assert normalizer_response['match_type'] == MatchType.TRADE_NAME
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == bifidobacterium_infantis.label
+    assert normalized_drug.concept_id == bifidobacterium_infantis.concept_id
+    assert set(normalized_drug.aliases) == \
+           set(bifidobacterium_infantis.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(bifidobacterium_infantis.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(bifidobacterium_infantis.xrefs)
+    assert set(normalized_drug.trade_names) == \
+           set(bifidobacterium_infantis.trade_names)
+    assert normalized_drug.approval_status == \
+           bifidobacterium_infantis.approval_status
+
+
+def test_cisplatin(cisplatin, rxnorm):
+    """Test that cisplatin drug normalizes to correct drug concept."""
+    # Concept ID Match
+    normalizer_response = rxnorm.normalize('RxCUI:2555')
+    assert normalizer_response['match_type'] == MatchType.CONCEPT_ID
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == cisplatin.label
+    assert normalized_drug.concept_id == cisplatin.concept_id
+    assert set(normalized_drug.aliases) == set(cisplatin.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(cisplatin.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(cisplatin.xrefs)
+    assert set(normalized_drug.trade_names) == set(cisplatin.trade_names)
+    assert normalized_drug.approval_status == cisplatin.approval_status
+
+    # Label Match
+    normalizer_response = rxnorm.normalize('CISPLATIN')
+    assert normalizer_response['match_type'] == MatchType.LABEL
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == cisplatin.label
+    assert normalized_drug.concept_id == cisplatin.concept_id
+    assert set(normalized_drug.aliases) == set(cisplatin.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(cisplatin.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(cisplatin.xrefs)
+    assert set(normalized_drug.trade_names) == set(cisplatin.trade_names)
+    assert normalized_drug.approval_status == cisplatin.approval_status
+
+    # Alias Match
+    normalizer_response = rxnorm.normalize('Cis-DDP')
+    assert normalizer_response['match_type'] == MatchType.ALIAS
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == cisplatin.label
+    assert normalized_drug.concept_id == cisplatin.concept_id
+    assert set(normalized_drug.aliases) == set(cisplatin.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(cisplatin.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(cisplatin.xrefs)
+    assert set(normalized_drug.trade_names) == set(cisplatin.trade_names)
+    assert normalized_drug.approval_status == cisplatin.approval_status
+
+    normalizer_response = rxnorm.normalize('Diaminedichloroplatinum')
+    assert normalizer_response['match_type'] == MatchType.ALIAS
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == cisplatin.label
+    assert normalized_drug.concept_id == cisplatin.concept_id
+    assert set(normalized_drug.aliases) == set(cisplatin.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(cisplatin.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(cisplatin.xrefs)
+    assert set(normalized_drug.trade_names) == set(cisplatin.trade_names)
+    assert normalized_drug.approval_status == cisplatin.approval_status
+
+    normalizer_response = rxnorm.normalize('cis Platinum')
+    assert normalizer_response['match_type'] == MatchType.ALIAS
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == cisplatin.label
+    assert normalized_drug.concept_id == cisplatin.concept_id
+    assert set(normalized_drug.aliases) == set(cisplatin.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(cisplatin.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(cisplatin.xrefs)
+    assert set(normalized_drug.trade_names) == set(cisplatin.trade_names)
+    assert normalized_drug.approval_status == cisplatin.approval_status
+
+    # Trade Name Match
+    normalizer_response = rxnorm.normalize('platinol')
+    assert normalizer_response['match_type'] == MatchType.TRADE_NAME
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == cisplatin.label
+    assert normalized_drug.concept_id == cisplatin.concept_id
+    assert set(normalized_drug.aliases) == set(cisplatin.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(cisplatin.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(cisplatin.xrefs)
+    assert set(normalized_drug.trade_names) == set(cisplatin.trade_names)
+    assert normalized_drug.approval_status == cisplatin.approval_status
+
+
+def test_amiloride(amiloride, rxnorm):
+    """Test that amiloride hydrochloride, anhydrous drug normalizes to
+    correct drug concept.
+    """
+    # Concept ID Match
+    normalizer_response = rxnorm.normalize('RxcUI:644')
+    assert normalizer_response['match_type'] == MatchType.CONCEPT_ID
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == amiloride.label
+    assert normalized_drug.concept_id == amiloride.concept_id
+    assert set(normalized_drug.aliases) == set(amiloride.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(amiloride.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(amiloride.xrefs)
+    assert set(normalized_drug.trade_names) == set(amiloride.trade_names)
+    assert normalized_drug.approval_status == amiloride.approval_status
+
+    # Label Match
+    normalizer_response = rxnorm.normalize('amiloride')
+    assert normalizer_response['match_type'] == MatchType.LABEL
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == amiloride.label
+    assert normalized_drug.concept_id == amiloride.concept_id
+    assert set(normalized_drug.aliases) == set(amiloride.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(amiloride.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(amiloride.xrefs)
+    assert set(normalized_drug.trade_names) == set(amiloride.trade_names)
+    assert normalized_drug.approval_status == amiloride.approval_status
+
+    # Alias Match
+    normalizer_response = rxnorm.normalize('Amyloride')
+    assert normalizer_response['match_type'] == MatchType.ALIAS
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == amiloride.label
+    assert normalized_drug.concept_id == amiloride.concept_id
+    assert set(normalized_drug.aliases) == set(amiloride.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(amiloride.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(amiloride.xrefs)
+    assert set(normalized_drug.trade_names) == set(amiloride.trade_names)
+    assert normalized_drug.approval_status == amiloride.approval_status
+
+    normalizer_response = rxnorm.normalize('3,5-diamino-N-carbamimidoyl-6-'
+                                           'chloropyrazine-2-carboxamide')
+    assert normalizer_response['match_type'] == MatchType.ALIAS
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == amiloride.label
+    assert normalized_drug.concept_id == amiloride.concept_id
+    assert set(normalized_drug.aliases) == set(amiloride.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(amiloride.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(amiloride.xrefs)
+    assert set(normalized_drug.trade_names) == set(amiloride.trade_names)
+    assert normalized_drug.approval_status == amiloride.approval_status
+
+    # Trade Name Match
+    normalizer_response = rxnorm.normalize('Midamor')
+    assert normalizer_response['match_type'] == MatchType.TRADE_NAME
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == amiloride.label
+    assert normalized_drug.concept_id == amiloride.concept_id
+    assert set(normalized_drug.aliases) == set(amiloride.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(amiloride.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(amiloride.xrefs)
+    assert set(normalized_drug.trade_names) == set(amiloride.trade_names)
+    assert normalized_drug.approval_status == amiloride.approval_status
+
+
+def test_timolol(timolol, rxnorm):
+    """Test that timolol drug normalizes to correct drug concept."""
+    # Concept ID Match
+    normalizer_response = rxnorm.normalize('RxcUI:10600')
+    assert normalizer_response['match_type'] == MatchType.CONCEPT_ID
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == timolol.label
+    assert normalized_drug.concept_id == timolol.concept_id
+    assert set(normalized_drug.aliases) == set(timolol.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(timolol.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(timolol.xrefs)
+    assert set(normalized_drug.trade_names) == set(timolol.trade_names)
+    assert normalized_drug.approval_status == timolol.approval_status
+
+    # Label Match
+    normalizer_response = rxnorm.normalize('timolol')
+    assert normalizer_response['match_type'] == MatchType.LABEL
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == timolol.label
+    assert normalized_drug.concept_id == timolol.concept_id
+    assert set(normalized_drug.aliases) == set(timolol.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(timolol.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(timolol.xrefs)
+    assert set(normalized_drug.trade_names) == set(timolol.trade_names)
+    assert normalized_drug.approval_status == timolol.approval_status
+
+    # Alias Match
+    normalizer_response = rxnorm.normalize('(S)-1-(tert-butylamino)-3-[(4-'
+                                           'morpholin-4-yl-1,2,5-thiadiazol'
+                                           '-3-yl)oxy]propan-2-ol')
+    assert normalizer_response['match_type'] == MatchType.ALIAS
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == timolol.label
+    assert normalized_drug.concept_id == timolol.concept_id
+    assert set(normalized_drug.aliases) == set(timolol.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(timolol.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(timolol.xrefs)
+    assert set(normalized_drug.trade_names) == set(timolol.trade_names)
+    assert normalized_drug.approval_status == timolol.approval_status
+
+    # Trade Name Match
+    normalizer_response = rxnorm.normalize('Betimol')
+    assert normalizer_response['match_type'] == MatchType.TRADE_NAME
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == timolol.label
+    assert normalized_drug.concept_id == timolol.concept_id
+    assert set(normalized_drug.aliases) == set(timolol.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(timolol.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(timolol.xrefs)
+    assert set(normalized_drug.trade_names) == set(timolol.trade_names)
+    assert normalized_drug.approval_status == timolol.approval_status
+
+
+def test_lymphocyte(lymphocyte, rxnorm):
+    """Test that lymphocyte drug normalizes to correct drug concept."""
+    # Concept ID Match
+    normalizer_response = rxnorm.normalize('RxCUI:1011')
+    assert normalizer_response['match_type'] == MatchType.CONCEPT_ID
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == lymphocyte.label
+    assert normalized_drug.concept_id == lymphocyte.concept_id
+    assert set(normalized_drug.aliases) == set(lymphocyte.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(lymphocyte.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(lymphocyte.xrefs)
+    assert set(normalized_drug.trade_names) == set(lymphocyte.trade_names)
+    assert normalized_drug.approval_status == lymphocyte.approval_status
+
+    # Label Match
+    normalizer_response = rxnorm.normalize('lymphocyte immune globulin, '
+                                           'anti-thymocyte globulin')
+    assert normalizer_response['match_type'] == MatchType.LABEL
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == lymphocyte.label
+    assert normalized_drug.concept_id == lymphocyte.concept_id
+    assert set(normalized_drug.aliases) == set(lymphocyte.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(lymphocyte.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(lymphocyte.xrefs)
+    assert set(normalized_drug.trade_names) == set(lymphocyte.trade_names)
+    assert normalized_drug.approval_status == lymphocyte.approval_status
+
+    # Alias Match
+    normalizer_response = rxnorm.normalize('Anti Thymocyte Globulin')
+    assert normalizer_response['match_type'] == MatchType.ALIAS
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == lymphocyte.label
+    assert normalized_drug.concept_id == lymphocyte.concept_id
+    assert set(normalized_drug.aliases) == set(lymphocyte.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(lymphocyte.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(lymphocyte.xrefs)
+    assert set(normalized_drug.trade_names) == set(lymphocyte.trade_names)
+    assert normalized_drug.approval_status == lymphocyte.approval_status
+
+    normalizer_response = rxnorm.normalize('Antithymoglobulin')
+    assert normalizer_response['match_type'] == MatchType.ALIAS
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == lymphocyte.label
+    assert normalized_drug.concept_id == lymphocyte.concept_id
+    assert set(normalized_drug.aliases) == set(lymphocyte.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(lymphocyte.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(lymphocyte.xrefs)
+    assert set(normalized_drug.trade_names) == set(lymphocyte.trade_names)
+    assert normalized_drug.approval_status == lymphocyte.approval_status
+
+    # Trade Name Match
+    normalizer_response = rxnorm.normalize('Thymoglobulin')
+    assert normalizer_response['match_type'] == MatchType.TRADE_NAME
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == lymphocyte.label
+    assert normalized_drug.concept_id == lymphocyte.concept_id
+    assert set(normalized_drug.aliases) == set(lymphocyte.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(lymphocyte.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(lymphocyte.xrefs)
+    assert set(normalized_drug.trade_names) == set(lymphocyte.trade_names)
+    assert normalized_drug.approval_status == lymphocyte.approval_status
+
+    normalizer_response = rxnorm.normalize('ATGAM')
+    assert normalizer_response['match_type'] == MatchType.TRADE_NAME
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == lymphocyte.label
+    assert normalized_drug.concept_id == lymphocyte.concept_id
+    assert set(normalized_drug.aliases) == set(lymphocyte.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(lymphocyte.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(lymphocyte.xrefs)
+    assert set(normalized_drug.trade_names) == set(lymphocyte.trade_names)
+    assert normalized_drug.approval_status == lymphocyte.approval_status
+
+
+def test_aspirin(aspirin, rxnorm):
+    """Test that aspirin drug normalizes to correct drug concept."""
+    # Concept ID Match
+    normalizer_response = rxnorm.normalize('RxcUI:1191')
+    assert normalizer_response['match_type'] == MatchType.CONCEPT_ID
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == aspirin.label
+    assert normalized_drug.concept_id == aspirin.concept_id
+    assert set(normalized_drug.aliases) == set(aspirin.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(aspirin.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(aspirin.xrefs)
+    assert set(normalized_drug.trade_names) == set(aspirin.trade_names)
+    assert normalized_drug.approval_status == aspirin.approval_status
+
+    # Label Match
+    normalizer_response = rxnorm.normalize('aspirin')
+    assert normalizer_response['match_type'] == MatchType.LABEL
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == aspirin.label
+    assert normalized_drug.concept_id == aspirin.concept_id
+    assert set(normalized_drug.aliases) == set(aspirin.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(aspirin.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(aspirin.xrefs)
+    assert set(normalized_drug.trade_names) == set(aspirin.trade_names)
+    assert normalized_drug.approval_status == aspirin.approval_status
+
+    # Alias Match
+    normalizer_response = rxnorm.normalize('Acetylsalicylic Acid')
+    assert normalizer_response['match_type'] == MatchType.ALIAS
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == aspirin.label
+    assert normalized_drug.concept_id == aspirin.concept_id
+    assert set(normalized_drug.aliases) == set(aspirin.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(aspirin.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(aspirin.xrefs)
+    assert set(normalized_drug.trade_names) == set(aspirin.trade_names)
+    assert normalized_drug.approval_status == aspirin.approval_status
+
+    normalizer_response = rxnorm.normalize('ASA')
+    assert normalizer_response['match_type'] == MatchType.ALIAS
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == aspirin.label
+    assert normalized_drug.concept_id == aspirin.concept_id
+    assert set(normalized_drug.aliases) == set(aspirin.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(aspirin.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(aspirin.xrefs)
+    assert set(normalized_drug.trade_names) == set(aspirin.trade_names)
+    assert normalized_drug.approval_status == aspirin.approval_status
+
+    # (Trade Name) No Match
+    normalizer_response = rxnorm.normalize('Anacin')
+    assert normalizer_response['match_type'] == MatchType.NO_MATCH
+    assert len(normalizer_response['records']) == 0
+
+
+def test_mesnan(mesna, rxnorm):
+    """Test that mesna drug normalizes to correct drug concept."""
+    # Concept ID Match
+    normalizer_response = rxnorm.normalize('RxCUI:44')
+    assert normalizer_response['match_type'] == MatchType.CONCEPT_ID
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == mesna.label
+    assert normalized_drug.concept_id == mesna.concept_id
+    assert set(normalized_drug.aliases) == set(mesna.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(mesna.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(mesna.xrefs)
+    assert set(normalized_drug.trade_names) == set(mesna.trade_names)
+    assert normalized_drug.approval_status == mesna.approval_status
+
+    # Label Match
+    normalizer_response = rxnorm.normalize('mesna')
+    assert normalizer_response['match_type'] == MatchType.LABEL
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == mesna.label
+    assert normalized_drug.concept_id == mesna.concept_id
+    assert set(normalized_drug.aliases) == set(mesna.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(mesna.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(mesna.xrefs)
+    assert set(normalized_drug.trade_names) == set(mesna.trade_names)
+    assert normalized_drug.approval_status == mesna.approval_status
+
+    # Alias Match
+    normalizer_response = rxnorm.normalize('Mesnum')
+    assert normalizer_response['match_type'] == MatchType.ALIAS
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == mesna.label
+    assert normalized_drug.concept_id == mesna.concept_id
+    assert set(normalized_drug.aliases) == set(mesna.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(mesna.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(mesna.xrefs)
+    assert set(normalized_drug.trade_names) == set(mesna.trade_names)
+    assert normalized_drug.approval_status == mesna.approval_status
+
+    normalizer_response = rxnorm.normalize('Ethanesulfonic acid, 2-mercapto-, '
+                                           'monosodium salt')
+    assert normalizer_response['match_type'] == MatchType.ALIAS
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == mesna.label
+    assert normalized_drug.concept_id == mesna.concept_id
+    assert set(normalized_drug.aliases) == set(mesna.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(mesna.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(mesna.xrefs)
+    assert set(normalized_drug.trade_names) == set(mesna.trade_names)
+    assert normalized_drug.approval_status == mesna.approval_status
+
+    # Trade Name Match
+    normalizer_response = rxnorm.normalize('Mesnex')
+    assert normalizer_response['match_type'] == MatchType.TRADE_NAME
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == mesna.label
+    assert normalized_drug.concept_id == mesna.concept_id
+    assert set(normalized_drug.aliases) == set(mesna.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(mesna.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(mesna.xrefs)
+    assert set(normalized_drug.trade_names) == set(mesna.trade_names)
+    assert normalized_drug.approval_status == mesna.approval_status
+
+
+def test_beta_alanine(beta_alanine, rxnorm):
+    """Test that beta_alanine drug normalizes to correct drug concept."""
+    # Concept ID Match
+    normalizer_response = rxnorm.normalize('RxCUI:61')
+    assert normalizer_response['match_type'] == MatchType.CONCEPT_ID
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == beta_alanine.label
+    assert normalized_drug.concept_id == beta_alanine.concept_id
+    assert set(normalized_drug.aliases) == set(beta_alanine.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(beta_alanine.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(beta_alanine.xrefs)
+    assert set(normalized_drug.trade_names) == set(beta_alanine.trade_names)
+    assert normalized_drug.approval_status == beta_alanine.approval_status
+
+    # Label Match
+    normalizer_response = rxnorm.normalize('beta-alanine')
+    assert normalizer_response['match_type'] == MatchType.LABEL
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == beta_alanine.label
+    assert normalized_drug.concept_id == beta_alanine.concept_id
+    assert set(normalized_drug.aliases) == set(beta_alanine.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(beta_alanine.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(beta_alanine.xrefs)
+    assert set(normalized_drug.trade_names) == set(beta_alanine.trade_names)
+    assert normalized_drug.approval_status == beta_alanine.approval_status
+
+    # Alias Match
+    normalizer_response = rxnorm.normalize('3 Aminopropionic Acid')
+    assert normalizer_response['match_type'] == MatchType.ALIAS
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == beta_alanine.label
+    assert normalized_drug.concept_id == beta_alanine.concept_id
+    assert set(normalized_drug.aliases) == set(beta_alanine.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(beta_alanine.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(beta_alanine.xrefs)
+    assert set(normalized_drug.trade_names) == set(beta_alanine.trade_names)
+    assert normalized_drug.approval_status == beta_alanine.approval_status
+
+
+def test_algestone(algestone, rxnorm):
+    """Test that algestone drug normalizes to correct drug concept."""
+    # Concept ID Match
+    normalizer_response = rxnorm.normalize('RxCUI:595')
+    assert normalizer_response['match_type'] == MatchType.CONCEPT_ID
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == algestone.label
+    assert normalized_drug.concept_id == algestone.concept_id
+    assert set(normalized_drug.aliases) == set(algestone.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(algestone.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(algestone.xrefs)
+    assert set(normalized_drug.trade_names) == set(algestone.trade_names)
+    assert normalized_drug.approval_status == algestone.approval_status
+
+    # Label Match
+    normalizer_response = rxnorm.normalize('algestone')
+    assert normalizer_response['match_type'] == MatchType.LABEL
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == algestone.label
+    assert normalized_drug.concept_id == algestone.concept_id
+    assert set(normalized_drug.aliases) == set(algestone.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(algestone.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(algestone.xrefs)
+    assert set(normalized_drug.trade_names) == set(algestone.trade_names)
+    assert normalized_drug.approval_status == algestone.approval_status
+
+    # Alias Match
+    normalizer_response = rxnorm.normalize('Pregn-4-ene-3,20-dione, 16,'
+                                           '17-dihydroxy-, (16alpha)-')
+    assert normalizer_response['match_type'] == MatchType.ALIAS
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == algestone.label
+    assert normalized_drug.concept_id == algestone.concept_id
+    assert set(normalized_drug.aliases) == set(algestone.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(algestone.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(algestone.xrefs)
+    assert set(normalized_drug.trade_names) == set(algestone.trade_names)
+    assert normalized_drug.approval_status == algestone.approval_status
+
+
+def test_levothyroxine(levothyroxine, rxnorm):
+    """Test that levothyroxine drug normalizes to correct drug concept."""
+    # Concept ID Match
+    normalizer_response = rxnorm.normalize('RxCUI:10582')
+    assert normalizer_response['match_type'] == MatchType.CONCEPT_ID
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == levothyroxine.label
+    assert normalized_drug.concept_id == levothyroxine.concept_id
+    assert set(normalized_drug.aliases) == set(levothyroxine.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(levothyroxine.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(levothyroxine.xrefs)
+    assert set(normalized_drug.trade_names) == set(levothyroxine.trade_names)
+    assert normalized_drug.approval_status == levothyroxine.approval_status
+
+    # Label Match
+    normalizer_response = rxnorm.normalize('levothyroxine')
+    assert normalizer_response['match_type'] == MatchType.LABEL
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == levothyroxine.label
+    assert normalized_drug.concept_id == levothyroxine.concept_id
+    assert set(normalized_drug.aliases) == set(levothyroxine.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(levothyroxine.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(levothyroxine.xrefs)
+    assert set(normalized_drug.trade_names) == set(levothyroxine.trade_names)
+    assert normalized_drug.approval_status == levothyroxine.approval_status
+
+    # Trade Name Match
+    normalizer_response = rxnorm.normalize('Unithroid')
+    assert normalizer_response['match_type'] == MatchType.TRADE_NAME
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == levothyroxine.label
+    assert normalized_drug.concept_id == levothyroxine.concept_id
+    assert set(normalized_drug.aliases) == set(levothyroxine.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(levothyroxine.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(levothyroxine.xrefs)
+    assert set(normalized_drug.trade_names) == set(levothyroxine.trade_names)
+    assert normalized_drug.approval_status == levothyroxine.approval_status
+
+    normalizer_response = rxnorm.normalize('Euthyrox')
+    assert normalizer_response['match_type'] == MatchType.TRADE_NAME
+    assert len(normalizer_response['records']) == 1
+    normalized_drug = normalizer_response['records'][0]
+    assert normalized_drug.label == levothyroxine.label
+    assert normalized_drug.concept_id == levothyroxine.concept_id
+    assert set(normalized_drug.aliases) == set(levothyroxine.aliases)
+    assert set(normalized_drug.other_identifiers) == \
+           set(levothyroxine.other_identifiers)
+    assert set(normalized_drug.xrefs) == set(levothyroxine.xrefs)
+    assert set(normalized_drug.trade_names) == set(levothyroxine.trade_names)
+    assert normalized_drug.approval_status == levothyroxine.approval_status
+
+
+def test_no_match(rxnorm):
+    """Test that a term normalizes to correct drug concept as a NO match."""
+    # Misspelled name
+    normalizer_response = rxnorm.normalize('17-hydroxycorticosteroi')
+    assert normalizer_response['match_type'] == MatchType.NO_MATCH
+    assert len(normalizer_response['records']) == 0
+
+    # Not storing foreign synonyms
+    normalizer_response = rxnorm.normalize('cisplatino')
+    assert normalizer_response['match_type'] == MatchType.NO_MATCH
+    assert len(normalizer_response['records']) == 0
+
+    # Wrong Namespace
+    normalizer_response = rxnorm.normalize('rxnorm:3')
+    assert normalizer_response['match_type'] == MatchType.NO_MATCH
+
+    # Test white space in between id
+    normalizer_response = rxnorm.normalize('rxcui: 3')
+    assert normalizer_response['match_type'] == MatchType.NO_MATCH
+
+    # Should not store brand name concepts as identity record
+    normalizer_response = rxnorm.normalize('rxcui:202433')
+    assert normalizer_response['match_type'] == MatchType.NO_MATCH
+
+    # Test empty query
+    normalizer_response = rxnorm.normalize('')
+    assert normalizer_response['match_type'] == MatchType.NO_MATCH
+    assert len(normalizer_response['records']) == 0
+
+
+def test_meta_info(rxnorm):
+    """Test that the meta field is correct."""
+    normalizer_response = rxnorm.fetch_meta()
+    assert normalizer_response.data_license == 'UMLS Metathesaurus'
+    assert normalizer_response.data_license_url == \
+           'https://www.nlm.nih.gov/research/umls/rxnorm/docs/' \
+           'termsofservice.html'
+    assert normalizer_response.version == '20210104'
+    assert normalizer_response.data_url == \
+           'https://www.nlm.nih.gov/research/umls/rxnorm/docs/rxnormfiles.html'
+    assert not normalizer_response.rdp_url
+    assert normalizer_response.data_license_attributes == {
+        "non_commercial": False,
+        "share_alike": False,
+        "attribution": False
+    }
diff --git a/tests/unit/test_therapy_query.py b/tests/unit/test_therapy_query.py
index 702a9486..011f60d9 100644
--- a/tests/unit/test_therapy_query.py
+++ b/tests/unit/test_therapy_query.py
@@ -25,7 +25,7 @@ def test_query(normalizer):
     assert resp['query'] == 'cisplatin'
     matches = resp['source_matches']
     assert isinstance(matches, list)
-    assert len(matches) == 4
+    assert len(matches) == 6
     wikidata = list(filter(lambda m: m['source'] == 'Wikidata',
                            matches))[0]
     assert len(wikidata['records']) == 1
@@ -35,12 +35,12 @@ def test_query(normalizer):
 
 def test_query_keyed(normalizer):
     """Test that query structures matches as dict when requested."""
-    resp = normalizer.normalize('cisplatin', keyed=True)
+    resp = normalizer.normalize('penicillin v', keyed=True)
     matches = resp['source_matches']
     assert isinstance(matches, dict)
-    wikidata = matches['Wikidata']
-    wikidata_record = wikidata['records'][0]
-    assert wikidata_record.label == 'cisplatin'
+    chemidplus = matches['ChemIDplus']
+    chemidplus_record = chemidplus['records'][0]
+    assert chemidplus_record.label == 'Penicillin V'
 
 
 def test_query_specify_normalizers(normalizer):
@@ -48,7 +48,7 @@ def test_query_specify_normalizers(normalizer):
     # test blank params
     resp = normalizer.normalize('cisplatin', keyed=True)
     matches = resp['source_matches']
-    assert len(matches) == 4
+    assert len(matches) == 6
     assert 'Wikidata' in matches
     assert 'ChEMBL' in matches
     assert 'NCIt' in matches
@@ -64,23 +64,25 @@ def test_query_specify_normalizers(normalizer):
     assert 'DrugBank' not in matches
 
     # test full inclusion
+    sources = 'chembl,ncit,drugbank,wikidata,rxnorm,chemidplus'
     resp = normalizer.normalize('cisplatin', keyed=True,
-                                incl='chembl,wikidata', excl='')
+                                incl=sources, excl='')
     matches = resp['source_matches']
-    assert len(matches) == 2
+    assert len(matches) == 6
     assert 'Wikidata' in matches
     assert 'ChEMBL' in matches
 
     # test partial exclusion
-    resp = normalizer.normalize('cisplatin', keyed=True, excl='chembl')
+    resp = normalizer.normalize('cisplatin', keyed=True, excl='chemidplus')
     matches = resp['source_matches']
-    assert len(matches) == 3
+    assert len(matches) == 5
     assert 'Wikidata' in matches
-    assert 'ChEMBL' not in matches
+    assert 'ChemIDplus' not in matches
 
     # test full exclusion
     resp = normalizer.normalize('cisplatin', keyed=True,
-                                excl='chembl, wikidata, drugbank, ncit')
+                                excl='chembl, wikidata, drugbank, ncit, '
+                                     'rxnorm, chemidplus')
     matches = resp['source_matches']
     assert len(matches) == 0
     assert 'Wikidata' not in matches
diff --git a/tests/unit/test_wikidata_source.py b/tests/unit/test_wikidata_source.py
index f7d0cde6..cc6f8c9f 100644
--- a/tests/unit/test_wikidata_source.py
+++ b/tests/unit/test_wikidata_source.py
@@ -38,11 +38,11 @@ def cisplatin():
         'approval_status': None,
         'other_identifiers': [
             'chembl:CHEMBL11359',
-            'drugbank:DB00515'
-        ],
-        'xrefs': [
+            'drugbank:DB00515',
             'rxcui:2555',
             'chemidplus:15663-27-1',
+        ],
+        'xrefs': [
             'pubchem.compound:5702198'
         ],
         'trade_names': []
@@ -70,12 +70,11 @@ def interferon_alfacon_1():
         'approval_status': None,
         'other_identifiers': [
             'chembl:CHEMBL1201557',
-            'drugbank:DB00069'
-        ],
-        'xrefs': [
+            'drugbank:DB00069',
             'rxcui:59744',
             'chemidplus:118390-30-0'
         ],
+        'xrefs': [],
         'trade_names': []
     }
     return Drug(**params)
@@ -92,12 +91,12 @@ def d_methamphetamine():
         'approval_status': None,
         'other_identifiers': [
             'chembl:CHEMBL1201201',
-            'drugbank:DB01577'
+            'drugbank:DB01577',
+            'rxcui:6816',
+            'chemidplus:537-46-2',
         ],
         'xrefs': [
-            'chemidplus:537-46-2',
             'pubchem.compound:10836',
-            'rxcui:6816'
         ],
         'trade_names': []
     }
@@ -129,10 +128,10 @@ def atropine():
             '8-Methyl-8-azabicyclo[3.2.1]oct-3-yl tropate'
         ],
         'approval_status': None,
-        'other_identifiers': [],
-        'xrefs': [
+        'other_identifiers': [
             'rxcui:1223'
         ],
+        'xrefs': [],
         'trade_names': []
     }
     return Drug(**params)