Repository: nmdp-bioinformatics/py-ard (language: python)

Issue:
py-ard should only accept valid IMGT db versions
Check github branches


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 6f79cdf..78137ad 100644
--- a/README.md
+++ b/README.md
@@ -48,21 +48,30 @@ Import `pyard` package.
 import pyard
 ```
 
-The cache size of pre-computed reductions can be changed from the default of 1000 (_not working_: will be fixed in a later release.)
-```python
-pyard.max_cache_size = 1_000_000
-```
 
 Initialize `ARD` object with a version of IMGT HLA database
 
 ```python
-ard = pyard.ARD(3290)
+import pyard
+
+ard = pyard.init('3510')
+```
+
+The cache size of pre-computed reductions can be changed from the default of 1000
+```python
+import pyard
+
+max_cache_size = 1_000_000
+ard = pyard.init('3510', cache_size=max_cache_size)
+
 ```
 
 You can specify a different directory for the cached data.
 
 ```python
-ard = pyard.ARD('3290', data_dir='/tmp/py-ard')
+import pyard.ard
+
+ard = pyard.init('3510', data_dir='/tmp/py-ard')
 ```
 
 You can choose to refresh the MAC code for current IMGT HLA database version
@@ -74,7 +83,9 @@ ard.refresh_mac_codes()
 The default initialization is to use the latest IMGT HLA database
 
 ```python
-ard = pyard.ARD()
+import pyard
+
+ard = pyard.init()
 ```
 
 ### Reduce Typings
@@ -84,13 +95,13 @@ Reduce a single locus HLA Typing.
 ```python
 allele = "A*01:01:01"
 
-ard.redux_gl(allele, 'G')
+ard.redux(allele, 'G')
 # >>> 'A*01:01:01G'
 
-ard.redux_gl(allele, 'lg')
+ard.redux(allele, 'lg')
 # >>> 'A*01:01g'
 
-ard.redux_gl(allele, 'lgx')
+ard.redux(allele, 'lgx')
 # >>> 'A*01:01'
 ```
 
@@ -99,14 +110,14 @@ Reduce an ambiguous GL String
 ```python
 # Reduce GL String
 #
-ard.redux_gl("A*01:01/A*01:01N+A*02:AB^B*07:02+B*07:AB", "G")
+ard.redux("A*01:01/A*01:01N+A*02:AB^B*07:02+B*07:AB", "G")
 # 'B*07:02:01G+B*07:02:01G^A*01:01:01G+A*02:01:01G/A*02:02'
 ```
 
 You can also reduce serology based typings.
 
 ```python
-ard.redux_gl('B14', 'lg')
+ard.redux('B14', 'lg')
 # >>> 'B*14:01g/B*14:02g/B*14:03g/B*14:04g/B*14:05g/B*14:06g/B*14:08g/B*14:09g/B*14:10g/B*14:11g/B*14:12g/B*14:13g/B*14:14g/B*14:15g/B*14:16g/B*14:17g/B*14:18g/B*14:19g/B*14:20g/B*14:21g/B*14:22g/B*14:23g/B*14:24g/B*14:25g/B*14:26g/B*14:27g/B*14:28g/B*14:29g/B*14:30g/B*14:31g/B*14:32g/B*14:33g/B*14:34g/B*14:35g/B*14:36g/B*14:37g/B*14:38g/B*14:39g/B*14:40g/B*14:42g/B*14:43g/B*14:44g/B*14:45g/B*14:46g/B*14:47g/B*14:48g/B*14:49g/B*14:50g/B*14:51g/B*14:52g/B*14:53g/B*14:54g/B*14:55g/B*14:56g/B*14:57g/B*14:58g/B*14:59g/B*14:60g/B*14:62g/B*14:63g/B*14:65g/B*14:66g/B*14:68g/B*14:70Qg/B*14:71g/B*14:73g/B*14:74g/B*14:75g/B*14:77g/B*14:82g/B*14:83g/B*14:86g/B*14:87g/B*14:88g/B*14:90g/B*14:93g/B*14:94g/B*14:95g/B*14:96g/B*14:97g/B*14:99g/B*14:102g'
 ```
 
diff --git a/api-spec.yaml b/api-spec.yaml
index aa80fc8..0137313 100644
--- a/api-spec.yaml
+++ b/api-spec.yaml
@@ -2,7 +2,7 @@ openapi: 3.0.3
 info:
   title: ARD Reduction
   description: Reduce to ARD Level
-  version: "0.9.1"
+  version: "1.0.0rc1"
 servers:
   - url: 'http://localhost:8080'
 tags:
@@ -14,6 +14,8 @@ tags:
     description: Expand MAC to alleles
   - name: DRBX Blender
     description: Blend DRBX based on DRB1 and DRB3/4/5
+  - name: Validation
+    description: Validate a GL String or Allele
 paths:
   /version:
     get:
@@ -134,7 +136,7 @@ paths:
   /validate:
     post:
       tags:
-        - ARD Reduction
+        - Validation
       operationId: api.validate_controller
       summary: Validate GL String
       description: |
diff --git a/api.py b/api.py
index 842bcca..837c654 100644
--- a/api.py
+++ b/api.py
@@ -1,12 +1,11 @@
 from flask import request
 
 import pyard
-from pyard import ARD
 from pyard.blender import DRBXBlenderError
 from pyard.exceptions import PyArdError, InvalidAlleleError
 
 # Globally accessible for all endpoints
-ard = ARD()
+ard = pyard.init()
 
 
 def validate_controller():
@@ -40,8 +39,8 @@ def redux_controller():
             return {"message": "gl_string and reduction_method not provided"}, 404
         # Perform redux
         try:
-            redux_gl_string = ard.redux_gl(gl_string, reduction_method)
-            return {"ard": redux_gl_string}, 200
+            redux_string = ard.redux(gl_string, reduction_method)
+            return {"ard": redux_string}, 200
         except PyArdError as e:
             return {"message": e.message}, 400
 
diff --git a/pyard/__init__.py b/pyard/__init__.py
index 4d8b7a5..16120fd 100644
--- a/pyard/__init__.py
+++ b/pyard/__init__.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+from functools import lru_cache
 
 #
 #    pyard pyARD.
@@ -21,10 +22,29 @@
 #    > http://www.fsf.org/licensing/licenses/lgpl.html
 #    > http://www.opensource.org/licenses/lgpl-license.php
 #
-from .pyard import ARD
 from .blender import blender as dr_blender
 from .broad_splits import find_splits as find_broad_splits
 from .misc import get_imgt_db_versions as db_versions
+from .misc import DEFAULT_CACHE_SIZE
 
 __author__ = """NMDP Bioinformatics"""
-__version__ = "0.9.1"
+__version__ = "1.0.0rc1"
+
+
+def init(
+    imgt_version: str = "Latest",
+    data_dir: str = None,
+    load_mac: bool = True,
+    cache_size: int = DEFAULT_CACHE_SIZE,
+    config: dict = None,
+):
+    from .ard import ARD
+
+    ard = ARD(
+        imgt_version=imgt_version,
+        data_dir=data_dir,
+        load_mac=load_mac,
+        max_cache_size=cache_size,
+        config=config,
+    )
+    return ard
diff --git a/pyard/pyard.py b/pyard/ard.py
similarity index 91%
rename from pyard/pyard.py
rename to pyard/ard.py
index cfc71bb..9bad4c2 100644
--- a/pyard/pyard.py
+++ b/pyard/ard.py
@@ -24,23 +24,23 @@
 import functools
 import sys
 import re
-from typing import Iterable, Literal, List
+from typing import Iterable, List
 
 from . import db
 from . import data_repository as dr
 from . import broad_splits
 from .smart_sort import smart_sort_comparator
 from .exceptions import InvalidAlleleError, InvalidMACError, InvalidTypingError
-from .misc import get_n_field_allele, get_2field_allele, expression_chars
-
-HLA_regex = re.compile("^HLA-")
-
-# Set the lru cache size with
-# >>> import pyard
-# >>> pyard.max_cache_size = 1_000_000
-# >>> ard = pyard.ARD()
+from .misc import (
+    get_n_field_allele,
+    get_2field_allele,
+    expression_chars,
+    DEFAULT_CACHE_SIZE,
+    HLA_regex,
+    VALID_REDUCTION_TYPES,
+    validate_reduction_type,
+)
 
-max_cache_size = 1_000
 default_config = {
     "reduce_serology": True,
     "reduce_v2": True,
@@ -54,22 +54,7 @@
     "verbose_log": True,
 }
 
-reduction_types = (
-    "G",
-    "lg",
-    "lgx",
-    "W",
-    "exon",
-    "U2",  # Unambiguous Reduction to 2 fields
-)
-
 # Typing information
-VALID_REDUCTION_TYPES = Literal["G", "lg", "lgx", "W", "exon", "U2"]
-
-
-def validate_reduction_type(ars_type):
-    if ars_type not in reduction_types:
-        raise ValueError(f"Reduction type needs to be one of {reduction_types}")
 
 
 class ARD(object):
@@ -84,6 +69,7 @@ def __init__(
         imgt_version: str = "Latest",
         data_dir: str = None,
         load_mac: bool = True,
+        max_cache_size: int = DEFAULT_CACHE_SIZE,
         config: dict = None,
     ):
         """
@@ -103,8 +89,6 @@ def __init__(
         # Create a database connection for writing
         self.db_connection, _ = db.create_db_connection(data_dir, imgt_version)
 
-        # Load MAC codes
-        dr.generate_mac_codes(self.db_connection, refresh_mac=False, load_mac=load_mac)
         # Load ARS mappings
         self.ars_mappings, p_group = dr.generate_ars_mapping(
             self.db_connection, imgt_version
@@ -132,10 +116,19 @@ def __init__(
         dr.generate_v2_to_v3_mapping(self.db_connection, imgt_version)
         # Save IMGT database version
         dr.set_db_version(self.db_connection, imgt_version)
+        # Load MAC codes
+        dr.generate_mac_codes(self.db_connection, refresh_mac=False, load_mac=load_mac)
 
         # Close the current read-write db connection
         self.db_connection.close()
 
+        # Adjust the cache for redux
+        if max_cache_size != DEFAULT_CACHE_SIZE:
+            self._redux_allele = functools.lru_cache(maxsize=max_cache_size)(
+                self._redux_allele
+            )
+            self.redux = functools.lru_cache(maxsize=max_cache_size)(self.redux)
+
         # reference data is read-only and can be frozen
         # Works only for Python >= 3.9
         if sys.version_info.major == 3 and sys.version_info.minor >= 9:
@@ -154,8 +147,10 @@ def __del__(self):
         if hasattr(self, "db_connection") and self.db_connection:
             self.db_connection.close()
 
-    @functools.lru_cache(maxsize=max_cache_size)
-    def redux(self, allele: str, redux_type: VALID_REDUCTION_TYPES, reping=True) -> str:
+    @functools.lru_cache(maxsize=DEFAULT_CACHE_SIZE)
+    def _redux_allele(
+        self, allele: str, redux_type: VALID_REDUCTION_TYPES, re_ping=True
+    ) -> str:
         """
         Does ARS reduction with allele and ARS type
 
@@ -172,7 +167,7 @@ def redux(self, allele: str, redux_type: VALID_REDUCTION_TYPES, reping=True) ->
         # deal with leading 'HLA-'
         if HLA_regex.search(allele):
             hla, allele_name = allele.split("-")
-            redux_allele = self.redux(allele_name, redux_type)
+            redux_allele = self._redux_allele(allele_name, redux_type)
             if redux_allele:
                 return "HLA-" + redux_allele
             else:
@@ -187,12 +182,12 @@ def redux(self, allele: str, redux_type: VALID_REDUCTION_TYPES, reping=True) ->
         if allele.endswith(("P", "G")):
             if redux_type in ["lg", "lgx", "G"]:
                 allele = allele[:-1]
-        if self._config["ping"] and reping:
+        if self._config["ping"] and re_ping:
             if redux_type in ("lg", "lgx", "U2"):
                 if allele in self.ars_mappings.p_not_g:
                     return self.ars_mappings.p_not_g[allele]
                 else:
-                    return self.redux(allele, redux_type, False)
+                    return self._redux_allele(allele, redux_type, False)
 
         if redux_type == "G" and allele in self.ars_mappings.g_group:
             if allele in self.ars_mappings.dup_g:
@@ -217,7 +212,7 @@ def redux(self, allele: str, redux_type: VALID_REDUCTION_TYPES, reping=True) ->
             if self._is_who_allele(allele):
                 return allele
             if allele in self.who_group:
-                return self.redux_gl("/".join(self.who_group[allele]), redux_type)
+                return self.redux("/".join(self.who_group[allele]), redux_type)
             else:
                 return allele
         elif redux_type == "exon":
@@ -246,7 +241,7 @@ def redux(self, allele: str, redux_type: VALID_REDUCTION_TYPES, reping=True) ->
                 return allele_2_fields
             else:
                 # If ambiguous, reduce to G group level
-                return self.redux(allele, "lgx")
+                return self._redux_allele(allele, "lgx")
         else:
             # TODO: make this an explicit lookup to the g_group or p_group table
             # just having a shorter name be valid is not stringent enough
@@ -257,13 +252,14 @@ def redux(self, allele: str, redux_type: VALID_REDUCTION_TYPES, reping=True) ->
             else:
                 raise InvalidAlleleError(f"{allele} is an invalid allele.")
 
-    def sorted_unique_gl(self, gls: List[str], delim: str) -> str:
+    @staticmethod
+    def sorted_unique_gl(gls: List[str], delim: str) -> str:
         """
         Make a list of sorted unique GL Strings separated by delim.
         As the list may itself contains elements that are separated by the
         delimiter, split the elements first and then make them unique.
 
-        :param gl: List of gl strings that need to be joined by delim
+        :param gls: List of gl strings that need to be joined by delim
         :param delim: Delimiter of concern
         :return: a GL string sorted and made of unique GL
         """
@@ -287,8 +283,8 @@ def sorted_unique_gl(self, gls: List[str], delim: str) -> str:
             sorted(unique_gls, key=functools.cmp_to_key(smart_sort_comparator))
         )
 
-    @functools.lru_cache(maxsize=max_cache_size)
-    def redux_gl(self, glstring: str, redux_type: VALID_REDUCTION_TYPES) -> str:
+    @functools.lru_cache(maxsize=DEFAULT_CACHE_SIZE)
+    def redux(self, glstring: str, redux_type: VALID_REDUCTION_TYPES) -> str:
         """
         Does ARS reduction with gl string and ARS type
 
@@ -306,38 +302,38 @@ def redux_gl(self, glstring: str, redux_type: VALID_REDUCTION_TYPES) -> str:
 
         if re.search(r"\^", glstring):
             return self.sorted_unique_gl(
-                [self.redux_gl(a, redux_type) for a in glstring.split("^")], "^"
+                [self.redux(a, redux_type) for a in glstring.split("^")], "^"
             )
 
         if re.search(r"\|", glstring):
             return self.sorted_unique_gl(
-                [self.redux_gl(a, redux_type) for a in glstring.split("|")], "|"
+                [self.redux(a, redux_type) for a in glstring.split("|")], "|"
             )
 
         if re.search(r"\+", glstring):
             return self.sorted_unique_gl(
-                [self.redux_gl(a, redux_type) for a in glstring.split("+")], "+"
+                [self.redux(a, redux_type) for a in glstring.split("+")], "+"
             )
 
         if re.search("~", glstring):
             return self.sorted_unique_gl(
-                [self.redux_gl(a, redux_type) for a in glstring.split("~")], "~"
+                [self.redux(a, redux_type) for a in glstring.split("~")], "~"
             )
 
         if re.search("/", glstring):
             return self.sorted_unique_gl(
-                [self.redux_gl(a, redux_type) for a in glstring.split("/")], "/"
+                [self.redux(a, redux_type) for a in glstring.split("/")], "/"
             )
 
         # Handle V2 to V3 mapping
         if self.is_v2(glstring):
             glstring = self._map_v2_to_v3(glstring)
-            return self.redux_gl(glstring, redux_type)
+            return self.redux(glstring, redux_type)
 
         # Handle Serology
         if self._config["reduce_serology"] and self.is_serology(glstring):
             alleles = self._get_alleles_from_serology(glstring)
-            return self.redux_gl("/".join(alleles), redux_type)
+            return self.redux("/".join(alleles), redux_type)
 
         if ":" in glstring:
             loc_allele = glstring.split(":")
@@ -354,14 +350,12 @@ def redux_gl(self, glstring: str, redux_type: VALID_REDUCTION_TYPES) -> str:
                 loc_antigen = loc_antigen.split("-")[1]
             if self.is_XX(glstring, loc_antigen, code):
                 if is_hla_prefix:
-                    reduced_alleles = self.redux_gl(
+                    reduced_alleles = self.redux(
                         "/".join(self.xx_codes[loc_antigen]), redux_type
                     )
                     return "/".join(["HLA-" + a for a in reduced_alleles.split("/")])
                 else:
-                    return self.redux_gl(
-                        "/".join(self.xx_codes[loc_antigen]), redux_type
-                    )
+                    return self.redux("/".join(self.xx_codes[loc_antigen]), redux_type)
 
         # Handle MAC
         if self._config["reduce_MAC"] and self.is_mac(glstring):
@@ -374,15 +368,15 @@ def redux_gl(self, glstring: str, redux_type: VALID_REDUCTION_TYPES) -> str:
                     alleles = ["HLA-" + a for a in alleles]
                 else:
                     alleles = self._get_alleles(code, loc_antigen)
-                return self.redux_gl("/".join(alleles), redux_type)
+                return self.redux("/".join(alleles), redux_type)
             else:
                 raise InvalidMACError(f"{glstring} is an invalid MAC.")
 
         # Handle short nulls
         if self._config["reduce_shortnull"] and self.is_shortnull(glstring):
-            return self.redux_gl("/".join(self.shortnulls[glstring]), redux_type)
+            return self.redux("/".join(self.shortnulls[glstring]), redux_type)
 
-        return self.redux(glstring, redux_type)
+        return self._redux_allele(glstring, redux_type)
 
     def validate(self, glstring):
         """
diff --git a/pyard/data_repository.py b/pyard/data_repository.py
index cb09084..8e87fa7 100644
--- a/pyard/data_repository.py
+++ b/pyard/data_repository.py
@@ -20,30 +20,30 @@
 #    > http://www.fsf.org/licensing/licenses/lgpl.html
 #    > http://www.opensource.org/licenses/lgpl-license.php
 #
-import sys
 import copy
-from collections import namedtuple
 import functools
 import sqlite3
-from urllib.error import HTTPError
+from collections import namedtuple
 
 import pandas as pd
 
+import pyard.load
+from pyard.smart_sort import smart_sort_comparator
 from . import db, broad_splits
-from .broad_splits import broad_splits_dna_mapping
-from .load import load_serology_broad_split_mapping
+from .load import (
+    load_g_group,
+    load_p_group,
+    load_allele_list,
+    load_serology_mappings,
+    load_latest_version,
+)
+from .misc import expression_chars
 from .misc import (
     get_2field_allele,
     get_3field_allele,
     number_of_fields,
     get_1field_allele,
 )
-from .misc import expression_chars, get_G_name, get_P_name
-
-# GitHub URL where IMGT HLA files are downloaded.
-from pyard.smart_sort import smart_sort_comparator
-
-IMGT_HLA_URL = "https://raw.githubusercontent.com/ANHIG/IMGTHLA/"
 
 ars_mapping_tables = [
     "dup_g",
@@ -84,44 +84,17 @@ def expression_reduce(df):
 
 def generate_ars_mapping(db_connection: sqlite3.Connection, imgt_version):
     if db.tables_exist(db_connection, ars_mapping_tables):
-        dup_g = db.load_dict(
-            db_connection, table_name="dup_g", columns=("allele", "g_group")
-        )
-        dup_lgx = db.load_dict(
-            db_connection, table_name="dup_lgx", columns=("allele", "lgx_group")
-        )
-        g_group = db.load_dict(
-            db_connection, table_name="g_group", columns=("allele", "g")
-        )
-        lgx_group = db.load_dict(
-            db_connection, table_name="lgx_group", columns=("allele", "lgx")
-        )
-        exon_group = db.load_dict(
-            db_connection, table_name="exon_group", columns=("allele", "exon")
-        )
-        p_not_g = db.load_dict(
-            db_connection, table_name="p_not_g", columns=("allele", "lgx")
-        )
-        return (
-            ARSMapping(
-                dup_g=dup_g,
-                dup_lgx=dup_lgx,
-                g_group=g_group,
-                lgx_group=lgx_group,
-                exon_group=exon_group,
-                p_not_g=p_not_g,
-            ),
-            None,
-        )
-
-    df = load_g_group(imgt_version)
+        return db.load_ars_mappings(db_connection)
 
+    df_g_group = load_g_group(imgt_version)
     df_p_group = load_p_group(imgt_version)
+
+    # Extract p group mapping
     p_group = df_p_group.set_index("A")["P"].to_dict()
 
-    # compare df_p_group["2d"] with df["2d"] to find 2-field alleles in the
+    # compare df_p_group["2d"] with df_g_group["2d"] to find 2-field alleles in the
     # P-group that aren't in the G-group
-    p_not_in_g = set(df_p_group["2d"]) - set(df["2d"])
+    p_not_in_g = set(df_p_group["2d"]) - set(df_g_group["2d"])
 
     # filter to find these 2-field alleles (2d) in the P-group data frame
     df_p_not_g = df_p_group[df_p_group["2d"].isin(p_not_in_g)]
@@ -133,7 +106,7 @@ def generate_ars_mapping(db_connection: sqlite3.Connection, imgt_version):
     # goal: identify 2-field alleles that are in multiple G-groups
 
     # group by 2d and G, and select the 2d column and count the columns
-    mg = df.drop_duplicates(["2d", "G"])["2d"].value_counts()
+    mg = df_g_group.drop_duplicates(["2d", "G"])["2d"].value_counts()
     # filter out the mg with count > 1, leaving only duplicates
     # take the index from the 2d version the data frame, make that a column
     # and turn that into a list
@@ -141,7 +114,7 @@ def generate_ars_mapping(db_connection: sqlite3.Connection, imgt_version):
 
     # Keep only the alleles that have more than 1 mapping
     dup_g = (
-        df[df["2d"].isin(multiple_g_list)][["G", "2d"]]
+        df_g_group[df_g_group["2d"].isin(multiple_g_list)][["G", "2d"]]
         .drop_duplicates()
         .groupby("2d", as_index=True)
         .agg("/".join)
@@ -149,12 +122,12 @@ def generate_ars_mapping(db_connection: sqlite3.Connection, imgt_version):
     )
 
     # multiple lgx
-    mlgx = df.drop_duplicates(["2d", "lgx"])["2d"].value_counts()
+    mlgx = df_g_group.drop_duplicates(["2d", "lgx"])["2d"].value_counts()
     multiple_lgx_list = mlgx[mlgx > 1].reset_index()["index"].to_list()
 
     # Keep only the alleles that have more than 1 mapping
     dup_lgx = (
-        df[df["2d"].isin(multiple_lgx_list)][["lgx", "2d"]]
+        df_g_group[df_g_group["2d"].isin(multiple_lgx_list)][["lgx", "2d"]]
         .drop_duplicates()
         .groupby("2d", as_index=True)
         .agg("/".join)
@@ -164,9 +137,9 @@ def generate_ars_mapping(db_connection: sqlite3.Connection, imgt_version):
     # Creating dictionaries with mac_code->ARS group mapping
     df_g = pd.concat(
         [
-            df[["2d", "G"]].rename(columns={"2d": "A"}),
-            df[["3d", "G"]].rename(columns={"3d": "A"}),
-            df[["A", "G"]],
+            df_g_group[["2d", "G"]].rename(columns={"2d": "A"}),
+            df_g_group[["3d", "G"]].rename(columns={"3d": "A"}),
+            df_g_group[["A", "G"]],
         ],
         ignore_index=True,
     )
@@ -174,9 +147,9 @@ def generate_ars_mapping(db_connection: sqlite3.Connection, imgt_version):
 
     df_lgx = pd.concat(
         [
-            df[["2d", "lgx"]].rename(columns={"2d": "A"}),
-            df[["3d", "lgx"]].rename(columns={"3d": "A"}),
-            df[["A", "lgx"]],
+            df_g_group[["2d", "lgx"]].rename(columns={"2d": "A"}),
+            df_g_group[["3d", "lgx"]].rename(columns={"3d": "A"}),
+            df_g_group[["A", "lgx"]],
         ]
     )
     lgx_group = df_lgx.set_index("A")["lgx"].to_dict()
@@ -184,174 +157,24 @@ def generate_ars_mapping(db_connection: sqlite3.Connection, imgt_version):
     # exon
     df_exon = pd.concat(
         [
-            df[["A", "3d"]].rename(columns={"3d": "exon"}),
+            df_g_group[["A", "3d"]].rename(columns={"3d": "exon"}),
         ]
     )
     exon_group = df_exon.set_index("A")["exon"].to_dict()
 
     # save
-    db.save_dict(
-        db_connection,
-        table_name="p_not_g",
-        dictionary=p_not_g,
-        columns=("allele", "lgx"),
-    )
-    db.save_dict(
-        db_connection,
-        table_name="dup_g",
-        dictionary=dup_g,
-        columns=("allele", "g_group"),
-    )
-    db.save_dict(
-        db_connection,
-        table_name="dup_lgx",
-        dictionary=dup_lgx,
-        columns=("allele", "lgx_group"),
-    )
-    db.save_dict(
-        db_connection, table_name="g_group", dictionary=g_group, columns=("allele", "g")
-    )
-    db.save_dict(
-        db_connection,
-        table_name="lgx_group",
-        dictionary=lgx_group,
-        columns=("allele", "lgx"),
-    )
-    db.save_dict(
-        db_connection,
-        table_name="exon_group",
-        dictionary=exon_group,
-        columns=("allele", "exon"),
-    )
-
-    return (
-        ARSMapping(
-            dup_g=dup_g,
-            dup_lgx=dup_lgx,
-            g_group=g_group,
-            lgx_group=lgx_group,
-            exon_group=exon_group,
-            p_not_g=p_not_g,
-        ),
-        p_group,
+    return db.save_ars_mappings(
+        db_connection, dup_g, dup_lgx, exon_group, g_group, lgx_group, p_group, p_not_g
     )
 
 
-def load_g_group(imgt_version):
-    # load the hla_nom_g.txt
-    ars_g_url = f"{IMGT_HLA_URL}{imgt_version}/wmda/hla_nom_g.txt"
-    df = pd.read_csv(ars_g_url, skiprows=6, names=["Locus", "A", "G"], sep=";").dropna()
-    # the G-group is named for its first allele
-    df["G"] = df["A"].apply(get_G_name)
-    # convert slash delimited string to a list
-    df["A"] = df["A"].apply(lambda a: a.split("/"))
-    # convert the list into separate rows for each element
-    df = df.explode("A")
-    #  A*   + 02:01   = A*02:01
-    df["A"] = df["Locus"] + df["A"]
-    df["G"] = df["Locus"] + df["G"]
-    # Create 2,3 field versions of the alleles
-    df["2d"] = df["A"].apply(get_2field_allele)
-    df["3d"] = df["A"].apply(get_3field_allele)
-    # lgx is 2 field version of the G group allele
-    df["lgx"] = df["G"].apply(get_2field_allele)
-
-    return df
-
-
-def load_p_group(imgt_version):
-    # load the hla_nom_p.txt
-    ars_p_url = f"{IMGT_HLA_URL}{imgt_version}/wmda/hla_nom_p.txt"
-    # example: C*;06:06:01:01/06:06:01:02/06:271;06:06P
-    df_p = pd.read_csv(
-        ars_p_url, skiprows=6, names=["Locus", "A", "P"], sep=";"
-    ).dropna()
-    # the P-group is named for its first allele
-    df_p["P"] = df_p["A"].apply(get_P_name)
-    # convert slash delimited string to a list
-    df_p["A"] = df_p["A"].apply(lambda a: a.split("/"))
-    df_p = df_p.explode("A")
-    # C* 06:06:01:01/06:06:01:02/06:271 06:06P
-    df_p["A"] = df_p["Locus"] + df_p["A"]
-    df_p["P"] = df_p["Locus"] + df_p["P"]
-    # C* 06:06:01:01 06:06P
-    # C* 06:06:01:02 06:06P
-    # C* 06:271 06:06P
-    df_p["2d"] = df_p["A"].apply(get_2field_allele)
-    # lgx has the P-group name without the P for comparison
-    df_p["lgx"] = df_p["P"].apply(get_2field_allele)
-    return df_p
-
-
 def generate_alleles_and_xx_codes_and_who(
     db_connection: sqlite3.Connection, imgt_version, ars_mappings, p_group
 ):
-    """
-    Checks to see if there's already an allele list file for the `imgt_version`
-    in the `data_dir` directory. If not, will download the file and create
-    a valid allele set and corresponding xx codes.
-
-    The format of the AlleleList file has a 6-line header with a header
-    on the 7th line
-    ```
-    # file: Allelelist.3290.txt
-    # date: 2017-07-10
-    # version: IPD-IMGT/HLA 3.29.0
-    # origin: https://github.com/ANHIG/IMGTHLA/Allelelist.3290.txt
-    # repository: https://raw.githubusercontent.com/ANHIG/IMGTHLA/Latest/allelelist/Allelelist.3290.txt
-    # author: WHO, Steven G. E. Marsh (steven.marsh@ucl.ac.uk)
-    AlleleID,Allele
-    HLA00001,A*01:01:01:01
-    HLA02169,A*01:01:01:02N
-    HLA14798,A*01:01:01:03
-    HLA15760,A*01:01:01:04
-    HLA16415,A*01:01:01:05
-    HLA16417,A*01:01:01:06
-    HLA16436,A*01:01:01:07
-    ```
-
-    :param db_connection: Database connection to the sqlite database
-    :param imgt_version: IMGT database version
-    :param ars_mappings: ARSMapping object to ARS mapping tables
-    :return: None, updates self
-    """
     if db.tables_exist(db_connection, code_mapping_tables):
-        valid_alleles = db.load_set(db_connection, "alleles")
-
-        who_alleles = db.load_set(db_connection, "who_alleles")
-        who_group = db.load_dict(db_connection, "who_group", ("who", "allele_list"))
-        who_group = {k: v.split("/") for k, v in who_group.items()}
-
-        xx_codes = db.load_dict(db_connection, "xx_codes", ("allele_1d", "allele_list"))
-        xx_codes = {k: v.split("/") for k, v in xx_codes.items()}
-
-        exp_alleles = db.load_dict(
-            db_connection, "exp_alleles", ("exp_allele", "allele_list")
-        )
-        exp_alleles = {k: v.split("/") for k, v in exp_alleles.items()}
-
-        return valid_alleles, who_alleles, xx_codes, who_group, exp_alleles
-
-    # Create a Pandas DataFrame from the mac_code list file
-    # Skip the header (first 6 lines) and use only the Allele column
-    if imgt_version == "Latest":
-        allele_list_url = f"{IMGT_HLA_URL}Latest/Allelelist.txt"
-    else:
-        if imgt_version == "3130":
-            # 3130 was renamed to 3131 for Allelelist file only 🤷🏾‍
-            imgt_version = "3131"
-        allele_list_url = (
-            f"{IMGT_HLA_URL}Latest/allelelist/Allelelist.{imgt_version}.txt"
-        )
+        return db.load_code_mappings(db_connection)
 
-    try:
-        allele_df = pd.read_csv(allele_list_url, header=6, usecols=["Allele"])
-    except HTTPError as e:
-        print(
-            f"Failed importing alleles for version {imgt_version} from {allele_list_url}",
-            file=sys.stderr,
-        )
-        sys.exit(1)
+    allele_df = load_allele_list(imgt_version)
 
     # Create a set of valid alleles
     # All 2-field, 3-field and the original Alleles are considered valid alleles
@@ -365,12 +188,6 @@ def generate_alleles_and_xx_codes_and_who(
     ]
     exp_alleles = exp_alleles.groupby("2d").apply(expression_reduce).dropna()
 
-    # flat_exp_alleles = {k: '/'.join(sorted(v, key=functools.cmp_to_key(smart_sort_comparator)))
-    #                 for k, v in exp_alleles.items()}
-    db.save_dict(
-        db_connection, "exp_alleles", exp_alleles, ("exp_allele", "allele_list")
-    )
-
     # Create valid set of alleles:
     # All full length alleles
     # All 3rd and 2nd field versions of longer alleles
@@ -397,19 +214,14 @@ def generate_alleles_and_xx_codes_and_who(
             else:
                 xx_codes[broad] = copy.deepcopy(xx_codes[split])
 
-    # Save this version of the valid alleles
-    db.save_set(db_connection, "alleles", valid_alleles, "allele")
     # Save this version of xx codes
     flat_xx_codes = {
         k: "/".join(sorted(v, key=functools.cmp_to_key(smart_sort_comparator)))
         for k, v in xx_codes.items()
     }
-    db.save_dict(db_connection, "xx_codes", flat_xx_codes, ("allele_1d", "allele_list"))
 
     # W H O
     who_alleles = allele_df["Allele"].to_list()
-    # Save this version of the WHO alleles
-    db.save_set(db_connection, "who_alleles", who_alleles, "allele")
 
     # Create WHO mapping from the unique alleles in the 1-field column
     allele_df["1d"] = allele_df["Allele"].apply(get_1field_allele)
@@ -441,8 +253,14 @@ def generate_alleles_and_xx_codes_and_who(
         k: "/".join(sorted(v, key=functools.cmp_to_key(smart_sort_comparator)))
         for k, v in who_group.items()
     }
-    db.save_dict(
-        db_connection, "who_group", flat_who_group, columns=("who", "allele_list")
+
+    db.save_code_mappings(
+        db_connection,
+        exp_alleles,
+        flat_who_group,
+        flat_xx_codes,
+        valid_alleles,
+        who_alleles,
     )
 
     return valid_alleles, who_alleles, xx_codes, who_group, exp_alleles
@@ -450,11 +268,7 @@ def generate_alleles_and_xx_codes_and_who(
 
 def generate_short_nulls(db_connection, who_group):
     if db.table_exists(db_connection, "shortnulls"):
-        shortnulls = db.load_dict(
-            db_connection, "shortnulls", ("shortnull", "allele_list")
-        )
-        shortnulls = {k: v.split("/") for k, v in shortnulls.items()}
-        return shortnulls
+        return db.load_shortnulls(db_connection)
 
     # shortnulls
     # scan WHO alleles for those with expression characters and make shortnull mappings
@@ -481,7 +295,8 @@ def generate_short_nulls(db_connection, who_group):
                 # e.g. DRB4*01:03N
                 shortnulls[a_shortnull] = "/".join(expression_alleles[a_shortnull])
 
-    db.save_dict(db_connection, "shortnulls", shortnulls, ("shortnull", "allele_list"))
+    db.save_shortnulls(db_connection, shortnulls)
+
     shortnulls = {k: v.split("/") for k, v in shortnulls.items()}
     return shortnulls
 
@@ -490,46 +305,6 @@ def generate_mac_codes(
     db_connection: sqlite3.Connection, refresh_mac: bool = False, load_mac: bool = True
 ):
     """
-    MAC files come in 2 different versions:
-
-    Martin: when they’re printed, the first is better for encoding and the
-    second is better for decoding. The entire list was maintained both as an
-    excel spreadsheet and also as a sybase database table. The excel was the
-    one that was printed and distributed.
-
-        **==> numer.v3.txt <==**
-
-        Sorted by the length and the the values in the list
-        ```
-        "LAST UPDATED: 09/30/20"
-        CODE	SUBTYPE
-
-        AB	01/02
-        AC	01/03
-        AD	01/04
-        AE	01/05
-        AG	01/06
-        AH	01/07
-        AJ	01/08
-        ```
-
-        **==> alpha.v3.txt <==**
-
-        Sorted by the code
-
-        ```
-        "LAST UPDATED: 10/01/20"
-        *	CODE	SUBTYPE
-
-            AA	01/02/03/05
-            AB	01/02
-            AC	01/03
-            AD	01/04
-            AE	01/05
-            AF	01/09
-            AG	01/06
-        ```
-
     :param db_connection: Database connection to the sqlite database
     :param refresh_mac: Refresh the database with newer MAC data ?
     :param load_mac: Should MAC be loaded at all
@@ -538,25 +313,10 @@ def generate_mac_codes(
     if load_mac:
         mac_table_name = "mac_codes"
         if refresh_mac or not db.table_exists(db_connection, mac_table_name):
-            # Load the MAC file to a DataFrame
-            mac_url = "https://hml.nmdp.org/mac/files/numer.v3.zip"
-            df_mac = pd.read_csv(
-                mac_url,
-                sep="\t",
-                compression="zip",
-                skiprows=3,
-                names=["Code", "Alleles"],
-                keep_default_na=False,
-            )
+            df_mac = pyard.load.load_mac_codes()
             # Create a dict from code to alleles
             mac = df_mac.set_index("Code")["Alleles"].to_dict()
-            # Save the mac dict to db
-            db.save_dict(
-                db_connection,
-                table_name=mac_table_name,
-                dictionary=mac,
-                columns=("code", "alleles"),
-            )
+            db.save_mac_codes(db_connection, mac, mac_table_name)
 
 
 def to_serological_name(locus_name: str):
@@ -580,28 +340,7 @@ def to_serological_name(locus_name: str):
 
 def generate_serology_mapping(db_connection: sqlite3.Connection, imgt_version):
     if not db.table_exists(db_connection, "serology_mapping"):
-        """
-        Read `rel_dna_ser.txt` file that contains alleles and their serological equivalents.
-
-        The fields of the Alleles->Serological mapping file are:
-           Locus - HLA Locus
-           Allele - HLA Allele Name
-           USA - Unambiguous Serological Antigen associated with allele
-           PSA - Possible Serological Antigen associated with allele
-           ASA - Assumed Serological Antigen associated with allele
-           EAE - Expert Assigned Exceptions in search determinants of some registries
-
-        EAE is ignored when generating the serology map.
-        """
-        rel_dna_ser_url = f"{IMGT_HLA_URL}{imgt_version}/wmda/rel_dna_ser.txt"
-        # Load WMDA serology mapping data from URL
-        df_sero = pd.read_csv(
-            rel_dna_ser_url,
-            sep=";",
-            skiprows=6,
-            names=["Locus", "Allele", "USA", "PSA", "ASA", "EAE"],
-            index_col=False,
-        )
+        df_sero = load_serology_mappings(imgt_version)
 
         # Remove 0 and ? from USA
         df_sero = df_sero[(df_sero["USA"] != "0") & (df_sero["USA"] != "?")]
@@ -658,39 +397,12 @@ def generate_serology_mapping(db_connection: sqlite3.Connection, imgt_version):
                 )
             )
 
-        # Save the serology mapping to db
-        db.save_dict(
-            db_connection,
-            table_name="serology_mapping",
-            dictionary=sero_mapping,
-            columns=("serology", "allele_list"),
-        )
+        db.save_serology_mappings(db_connection, sero_mapping)
 
 
 def generate_v2_to_v3_mapping(db_connection: sqlite3.Connection, imgt_version):
     if not db.table_exists(db_connection, "v2_mapping"):
-        # TODO: Create mapping table using both the allele list history and
-        #  deleted alleles as reference.
-        # Temporary Example
-        v2_to_v3_example = {
-            "A*0104": "A*01:04:01:01N",
-            "A*0105N": "A*01:04:01:01N",
-            "A*0111": "A*01:11N",
-            "A*01123": "A*01:123N",
-            "A*0115": "A*01:15N",
-            "A*0116": "A*01:16N",
-            "A*01160": "A*01:160N",
-            "A*01162": "A*01:162N",
-            "A*01178": "A*01:178N",
-            "A*01179": "A*01:179N",
-            "DRB5*02ZB": "DRB5*02:UTV",
-        }
-        db.save_dict(
-            db_connection,
-            table_name="v2_mapping",
-            dictionary=v2_to_v3_example,
-            columns=("v2", "v3"),
-        )
+        db.load_v2_v3_mappings(db_connection)
 
 
 def set_db_version(db_connection: sqlite3.Connection, imgt_version):
@@ -706,24 +418,14 @@ def set_db_version(db_connection: sqlite3.Connection, imgt_version):
     if version:
         return version
 
-    version = imgt_version
-
     if imgt_version == "Latest":
-        from urllib.request import urlopen
-
-        response = urlopen(
-            "https://raw.githubusercontent.com/ANHIG/IMGTHLA/Latest/release_version.txt"
-        )
-        for line in response:
-            l = line.decode("utf-8")
-            if l.find("version:") != -1:
-                # Version line looks like
-                # # version: IPD-IMGT/HLA 3.51.0
-                version = l.split()[-1].replace(".", "")
+        version = load_latest_version()
+    else:
+        version = imgt_version
 
     db.set_user_version(db_connection, int(version))
     print("Version:", version)
-    return db.get_user_version(db_connection)
+    return version
 
 
 def get_db_version(db_connection: sqlite3.Connection):
@@ -732,19 +434,8 @@ def get_db_version(db_connection: sqlite3.Connection):
 
 def generate_serology_broad_split_mapping(db_connection, imgt_version):
     if not db.table_exists(db_connection, "serology_broad_split_mapping"):
-        sero_mapping = load_serology_broad_split_mapping(imgt_version)
-        # Save the `splits` as a "/" delimited string to db
-        sero_splits = {sero: "/".join(splits) for sero, splits in sero_mapping.items()}
-        db.save_dict(
-            db_connection,
-            table_name="serology_broad_split_mapping",
-            dictionary=sero_splits,
-            columns=("serology", "splits"),
-        )
+        sero_mapping = pyard.load.load_serology_broad_split_mapping(imgt_version)
+        db.save_serology_broad_split_mappings(db_connection, sero_mapping)
         return sero_mapping
 
-    sero_mapping = db.load_dict(
-        db_connection, "serology_broad_split_mapping", ("serology", "splits")
-    )
-    sero_splits = {k: v.split("/") for k, v in sero_mapping.items()}
-    return sero_splits
+    return db.load_serology_broad_split_mappings(db_connection)
diff --git a/pyard/db.py b/pyard/db.py
index afce946..36a9236 100644
--- a/pyard/db.py
+++ b/pyard/db.py
@@ -20,24 +20,12 @@
 #    > http://www.fsf.org/licensing/licenses/lgpl.html
 #    > http://www.opensource.org/licenses/lgpl-license.php
 #
-import os
 import pathlib
 import sqlite3
-import tempfile
 from typing import Tuple, Dict, Set, List
 
-from pyard.misc import get_imgt_db_versions
-
-
-def get_pyard_db_default_directory():
-    """
-    The default directory is $TMPDIR/$USER-pyard
-    Check for `USERNAME` on Windows
-    @return: directory path to the default db directory
-    """
-    return pathlib.Path(tempfile.gettempdir()) / (
-        os.environ.get("USER", os.environ.get("USERNAME")) + "-pyard"
-    )
+from .data_repository import ARSMapping
+from .misc import get_imgt_db_versions, get_default_db_directory
 
 
 def create_db_connection(data_dir, imgt_version, ro=False):
@@ -52,7 +40,7 @@ def create_db_connection(data_dir, imgt_version, ro=False):
     """
     # Set data directory where all the downloaded files will go
     if data_dir is None:
-        data_dir = get_pyard_db_default_directory()
+        data_dir = get_default_db_directory()
 
     db_filename = f"{data_dir}/pyard-{imgt_version}.sqlite3"
 
@@ -219,7 +207,7 @@ def save_dict(
     connection: sqlite3.Connection,
     table_name: str,
     dictionary: Dict[str, str],
-    columns=Tuple[str, str],
+    columns: Tuple[str, str],
 ) -> bool:
     """
     Save the dictionary as a table with column names from columns Tuple.
@@ -378,3 +366,183 @@ def set_user_version(connection: sqlite3.Connection, version: int):
     connection.commit()
     # close the cursor
     cursor.close()
+
+
+def load_ars_mappings(db_connection):
+    dup_g = load_dict(db_connection, table_name="dup_g", columns=("allele", "g_group"))
+    dup_lgx = load_dict(
+        db_connection, table_name="dup_lgx", columns=("allele", "lgx_group")
+    )
+    g_group = load_dict(db_connection, table_name="g_group", columns=("allele", "g"))
+    lgx_group = load_dict(
+        db_connection, table_name="lgx_group", columns=("allele", "lgx")
+    )
+    exon_group = load_dict(
+        db_connection, table_name="exon_group", columns=("allele", "exon")
+    )
+    p_not_g = load_dict(db_connection, table_name="p_not_g", columns=("allele", "lgx"))
+    return (
+        ARSMapping(
+            dup_g=dup_g,
+            dup_lgx=dup_lgx,
+            g_group=g_group,
+            lgx_group=lgx_group,
+            exon_group=exon_group,
+            p_not_g=p_not_g,
+        ),
+        None,
+    )
+
+
+def save_ars_mappings(
+    db_connection, dup_g, dup_lgx, exon_group, g_group, lgx_group, p_group, p_not_g
+):
+    save_dict(
+        db_connection,
+        table_name="p_not_g",
+        dictionary=p_not_g,
+        columns=("allele", "lgx"),
+    )
+    save_dict(
+        db_connection,
+        table_name="dup_g",
+        dictionary=dup_g,
+        columns=("allele", "g_group"),
+    )
+    save_dict(
+        db_connection,
+        table_name="dup_lgx",
+        dictionary=dup_lgx,
+        columns=("allele", "lgx_group"),
+    )
+    save_dict(
+        db_connection, table_name="g_group", dictionary=g_group, columns=("allele", "g")
+    )
+    save_dict(
+        db_connection,
+        table_name="lgx_group",
+        dictionary=lgx_group,
+        columns=("allele", "lgx"),
+    )
+    save_dict(
+        db_connection,
+        table_name="exon_group",
+        dictionary=exon_group,
+        columns=("allele", "exon"),
+    )
+    return (
+        ARSMapping(
+            dup_g=dup_g,
+            dup_lgx=dup_lgx,
+            g_group=g_group,
+            lgx_group=lgx_group,
+            exon_group=exon_group,
+            p_not_g=p_not_g,
+        ),
+        p_group,
+    )
+
+
+def save_code_mappings(
+    db_connection,
+    exp_alleles,
+    flat_who_group,
+    flat_xx_codes,
+    valid_alleles,
+    who_alleles,
+):
+    save_dict(db_connection, "exp_alleles", exp_alleles, ("exp_allele", "allele_list"))
+    save_dict(db_connection, "xx_codes", flat_xx_codes, ("allele_1d", "allele_list"))
+    # Save this version of the valid alleles
+    save_set(db_connection, "alleles", valid_alleles, "allele")
+    # Save this version of the WHO alleles
+    save_set(db_connection, "who_alleles", who_alleles, "allele")
+    save_dict(
+        db_connection, "who_group", flat_who_group, columns=("who", "allele_list")
+    )
+
+
+def load_code_mappings(db_connection):
+    valid_alleles = load_set(db_connection, "alleles")
+    who_alleles = load_set(db_connection, "who_alleles")
+    who_group = load_dict(db_connection, "who_group", ("who", "allele_list"))
+    who_group = {k: v.split("/") for k, v in who_group.items()}
+    xx_codes = load_dict(db_connection, "xx_codes", ("allele_1d", "allele_list"))
+    xx_codes = {k: v.split("/") for k, v in xx_codes.items()}
+    exp_alleles = load_dict(db_connection, "exp_alleles", ("exp_allele", "allele_list"))
+    exp_alleles = {k: v.split("/") for k, v in exp_alleles.items()}
+    return valid_alleles, who_alleles, xx_codes, who_group, exp_alleles
+
+
+def load_shortnulls(db_connection):
+    shortnulls = load_dict(db_connection, "shortnulls", ("shortnull", "allele_list"))
+    shortnulls = {k: v.split("/") for k, v in shortnulls.items()}
+    return shortnulls
+
+
+def save_shortnulls(db_connection, shortnulls):
+    save_dict(db_connection, "shortnulls", shortnulls, ("shortnull", "allele_list"))
+
+
+def save_mac_codes(db_connection, mac, mac_table_name):
+    # Save the mac dict to db
+    save_dict(
+        db_connection,
+        table_name=mac_table_name,
+        dictionary=mac,
+        columns=("code", "alleles"),
+    )
+
+
+def save_serology_mappings(db_connection, sero_mapping):
+    # Save the serology mapping to db
+    save_dict(
+        db_connection,
+        table_name="serology_mapping",
+        dictionary=sero_mapping,
+        columns=("serology", "allele_list"),
+    )
+
+
+def load_v2_v3_mappings(db_connection):
+    # TODO: Create mapping table using both the allele list history and
+    #  deleted alleles as reference.
+    # Temporary Example
+    v2_to_v3_example = {
+        "A*0104": "A*01:04:01:01N",
+        "A*0105N": "A*01:04:01:01N",
+        "A*0111": "A*01:11N",
+        "A*01123": "A*01:123N",
+        "A*0115": "A*01:15N",
+        "A*0116": "A*01:16N",
+        "A*01160": "A*01:160N",
+        "A*01162": "A*01:162N",
+        "A*01178": "A*01:178N",
+        "A*01179": "A*01:179N",
+        "DRB5*02ZB": "DRB5*02:UTV",
+    }
+    save_dict(
+        db_connection,
+        table_name="v2_mapping",
+        dictionary=v2_to_v3_example,
+        columns=("v2", "v3"),
+    )
+
+
+def load_serology_broad_split_mappings(db_connection):
+    sero_mapping = load_dict(
+        db_connection, "serology_broad_split_mapping", ("serology", "splits")
+    )
+    sero_splits = {k: v.split("/") for k, v in sero_mapping.items()}
+    return sero_splits
+
+
+def save_serology_broad_split_mappings(db_connection, sero_mapping):
+    # Save the `splits` as a "/" delimited string to db
+    sero_splits = {sero: "/".join(splits) for sero, splits in sero_mapping.items()}
+    save_dict(
+        db_connection,
+        table_name="serology_broad_split_mapping",
+        dictionary=sero_splits,
+        columns=("serology", "splits"),
+    )
diff --git a/pyard/exceptions.py b/pyard/exceptions.py
index 0998a7e..0ebd570 100644
--- a/pyard/exceptions.py
+++ b/pyard/exceptions.py
@@ -3,12 +3,13 @@ class PyArdError(Exception):
     Base Class for All py-ard Errors
     """
 
-    pass
+    def __init__(self, message: str) -> None:
+        self.message = message
 
 
 class InvalidAlleleError(PyArdError):
     def __init__(self, message: str) -> None:
-        self.message = message
+        super().__init__(message)
 
     def __str__(self) -> str:
         return f"Invalid Allele: {self.message}"
@@ -16,7 +17,7 @@ def __str__(self) -> str:
 
 class InvalidMACError(PyArdError):
     def __init__(self, message: str) -> None:
-        self.message = message
+        super().__init__(message)
 
     def __str__(self) -> str:
         return f"Invalid MAC Code: {self.message}"
@@ -24,7 +25,6 @@ def __str__(self) -> str:
 
 class InvalidTypingError(PyArdError):
     def __init__(self, message: str, cause=None) -> None:
-        self.message = message
         self.cause = cause
 
     def __str__(self) -> str:
diff --git a/pyard/load.py b/pyard/load.py
index b1a745a..f3e6749 100644
--- a/pyard/load.py
+++ b/pyard/load.py
@@ -1,7 +1,12 @@
+import sys
 from typing import Dict, List
+from urllib.error import HTTPError
 
 import pandas as pd
 
+from pyard.misc import get_G_name, get_2field_allele, get_3field_allele, get_P_name
+
+# GitHub URL where IMGT HLA files are downloaded.
 IMGT_HLA_URL = "https://raw.githubusercontent.com/ANHIG/IMGTHLA/"
 
 
@@ -32,3 +37,194 @@ def load_serology_broad_split_mapping(imgt_version: str) -> Dict:
 
     sero_mapping = df_p[["Sero", "Splits"]].set_index("Sero")["Splits"].to_dict()
     return sero_mapping
+
+
+def load_g_group(imgt_version):
+    # load the hla_nom_g.txt
+    ars_g_url = f"{IMGT_HLA_URL}{imgt_version}/wmda/hla_nom_g.txt"
+    df = pd.read_csv(ars_g_url, skiprows=6, names=["Locus", "A", "G"], sep=";").dropna()
+    # the G-group is named for its first allele
+    df["G"] = df["A"].apply(get_G_name)
+    # convert slash delimited string to a list
+    df["A"] = df["A"].apply(lambda a: a.split("/"))
+    # convert the list into separate rows for each element
+    df = df.explode("A")
+    #  A*   + 02:01   = A*02:01
+    df["A"] = df["Locus"] + df["A"]
+    df["G"] = df["Locus"] + df["G"]
+    # Create 2,3 field versions of the alleles
+    df["2d"] = df["A"].apply(get_2field_allele)
+    df["3d"] = df["A"].apply(get_3field_allele)
+    # lgx is 2 field version of the G group allele
+    df["lgx"] = df["G"].apply(get_2field_allele)
+
+    return df
+
+
+def load_p_group(imgt_version):
+    # load the hla_nom_p.txt
+    ars_p_url = f"{IMGT_HLA_URL}{imgt_version}/wmda/hla_nom_p.txt"
+    # example: C*;06:06:01:01/06:06:01:02/06:271;06:06P
+    df_p = pd.read_csv(
+        ars_p_url, skiprows=6, names=["Locus", "A", "P"], sep=";"
+    ).dropna()
+    # the P-group is named for its first allele
+    df_p["P"] = df_p["A"].apply(get_P_name)
+    # convert slash delimited string to a list
+    df_p["A"] = df_p["A"].apply(lambda a: a.split("/"))
+    df_p = df_p.explode("A")
+    # C* 06:06:01:01/06:06:01:02/06:271 06:06P
+    df_p["A"] = df_p["Locus"] + df_p["A"]
+    df_p["P"] = df_p["Locus"] + df_p["P"]
+    # C* 06:06:01:01 06:06P
+    # C* 06:06:01:02 06:06P
+    # C* 06:271 06:06P
+    df_p["2d"] = df_p["A"].apply(get_2field_allele)
+    # lgx has the P-group name without the P for comparison
+    df_p["lgx"] = df_p["P"].apply(get_2field_allele)
+    return df_p
+
+
+def load_allele_list(imgt_version):
+    """
+    The format of the AlleleList file has a 6-line header with a header
+    on the 7th line
+    ```
+    # file: Allelelist.3290.txt
+    # date: 2017-07-10
+    # version: IPD-IMGT/HLA 3.29.0
+    # origin: https://github.com/ANHIG/IMGTHLA/Allelelist.3290.txt
+    # repository: https://raw.githubusercontent.com/ANHIG/IMGTHLA/Latest/allelelist/Allelelist.3290.txt
+    # author: WHO, Steven G. E. Marsh (steven.marsh@ucl.ac.uk)
+    AlleleID,Allele
+    HLA00001,A*01:01:01:01
+    HLA02169,A*01:01:01:02N
+    HLA14798,A*01:01:01:03
+    HLA15760,A*01:01:01:04
+    HLA16415,A*01:01:01:05
+    HLA16417,A*01:01:01:06
+    HLA16436,A*01:01:01:07
+    ```
+
+    :param db_connection: Database connection to the sqlite database
+    :param imgt_version: IMGT database version
+    :param ars_mappings: ARSMapping object to ARS mapping tables
+    :return: None, updates self
+    """
+    # Create a Pandas DataFrame from the mac_code list file
+    # Skip the header (first 6 lines) and use only the Allele column
+    if imgt_version == "Latest":
+        allele_list_url = f"{IMGT_HLA_URL}Latest/Allelelist.txt"
+    else:
+        if imgt_version == "3130":
+            # 3130 was renamed to 3131 for Allelelist file only 🤷🏾‍
+            imgt_version = "3131"
+        allele_list_url = (
+            f"{IMGT_HLA_URL}Latest/allelelist/Allelelist.{imgt_version}.txt"
+        )
+    try:
+        allele_df = pd.read_csv(allele_list_url, header=6, usecols=["Allele"])
+    except HTTPError as e:
+        print(
+            f"Failed importing alleles for version {imgt_version} from {allele_list_url}",
+            file=sys.stderr,
+        )
+        sys.exit(1)
+    return allele_df
+
+
+def load_serology_mappings(imgt_version):
+    """
+    Read `rel_dna_ser.txt` file that contains alleles and their serological equivalents.
+
+    The fields of the Alleles->Serological mapping file are:
+       Locus - HLA Locus
+       Allele - HLA Allele Name
+       USA - Unambiguous Serological Antigen associated with allele
+       PSA - Possible Serological Antigen associated with allele
+       ASA - Assumed Serological Antigen associated with allele
+       EAE - Expert Assigned Exceptions in search determinants of some registries
+
+    EAE is ignored when generating the serology map.
+    """
+    rel_dna_ser_url = f"{IMGT_HLA_URL}{imgt_version}/wmda/rel_dna_ser.txt"
+    # Load WMDA serology mapping data from URL
+    df_sero = pd.read_csv(
+        rel_dna_ser_url,
+        sep=";",
+        skiprows=6,
+        names=["Locus", "Allele", "USA", "PSA", "ASA", "EAE"],
+        index_col=False,
+    )
+    return df_sero
+
+
+def load_mac_codes():
+    """
+    MAC files come in 2 different versions:
+
+    Martin: when they’re printed, the first is better for encoding and the
+    second is better for decoding. The entire list was maintained both as an
+    Excel spreadsheet and also as a sybase database table. The Excel was the
+    one that was printed and distributed.
+
+        **==> numer.v3.txt <==**
+
+        Sorted by the length and the values in the list
+        ```
+        "LAST UPDATED: 09/30/20"
+        CODE	SUBTYPE
+
+        AB	01/02
+        AC	01/03
+        AD	01/04
+        AE	01/05
+        AG	01/06
+        AH	01/07
+        AJ	01/08
+        ```
+
+        **==> alpha.v3.txt <==**
+
+        Sorted by the code
+
+        ```
+        "LAST UPDATED: 10/01/20"
+        *	CODE	SUBTYPE
+
+            AA	01/02/03/05
+            AB	01/02
+            AC	01/03
+            AD	01/04
+            AE	01/05
+            AF	01/09
+            AG	01/06
+        ```
+    """
+    # Load the MAC file to a DataFrame
+    mac_url = "https://hml.nmdp.org/mac/files/numer.v3.zip"
+    df_mac = pd.read_csv(
+        mac_url,
+        sep="\t",
+        compression="zip",
+        skiprows=3,
+        names=["Code", "Alleles"],
+        keep_default_na=False,
+    )
+    return df_mac
+
+
+def load_latest_version():
+    from urllib.request import urlopen
+
+    response = urlopen(
+        "https://raw.githubusercontent.com/ANHIG/IMGTHLA/Latest/release_version.txt"
+    )
+    version = 0
+    for line in response:
+        l = line.decode("utf-8")
+        if l.find("version:") != -1:
+            # Version line looks like
+            # # version: IPD-IMGT/HLA 3.51.0
+            version = l.split()[-1].replace(".", "")
+    return version
diff --git a/pyard/misc.py b/pyard/misc.py
index 0263696..432536c 100644
--- a/pyard/misc.py
+++ b/pyard/misc.py
@@ -1,13 +1,18 @@
 # List of expression characters
 import pathlib
-from typing import List
+import re
+import tempfile
+from typing import List, Literal
 
-from pyard import db
+HLA_regex = re.compile("^HLA-")
 
+VALID_REDUCTION_TYPES = ["G", "lg", "lgx", "W", "exon", "U2"]
 expression_chars = ["N", "Q", "L", "S"]
 # List of P and G characters
 PandG_chars = ["P", "G"]
 
+DEFAULT_CACHE_SIZE = 1_000
+
 
 def get_n_field_allele(allele: str, n: int, preserve_expression=False) -> str:
     """
@@ -105,7 +110,7 @@ def get_data_dir(data_dir):
             raise RuntimeError(f"{data_dir} is not a valid directory")
         data_dir = path
     else:
-        data_dir = db.get_pyard_db_default_directory()
+        data_dir = get_default_db_directory()
     return data_dir
 
 
@@ -118,3 +123,12 @@ def get_imgt_version(imgt_version):
             f"{imgt_version} is not a valid IMGT database version number"
         )
     return "Latest"
+
+
+def get_default_db_directory():
+    return pathlib.Path(tempfile.gettempdir()) / "pyard"
+
+
+def validate_reduction_type(ars_type):
+    if ars_type not in VALID_REDUCTION_TYPES:
+        raise ValueError(f"Reduction type needs to be one of {VALID_REDUCTION_TYPES}")
diff --git a/scripts/pyard b/scripts/pyard
index 508e8f7..fa1cc4b 100755
--- a/scripts/pyard
+++ b/scripts/pyard
@@ -24,7 +24,7 @@
 import argparse
 import sys
 
-import pyard
+import pyard.misc
 from pyard.exceptions import InvalidAlleleError
 from pyard.misc import get_data_dir, get_imgt_version
 
@@ -56,7 +56,7 @@ if __name__ == "__main__":
     parser.add_argument("--gl", dest="gl_string", help="GL String to reduce")
     parser.add_argument(
         "-r",
-        choices=pyard.pyard.reduction_types,
+        choices=pyard.misc.VALID_REDUCTION_TYPES,
         dest="redux_type",
         help="Reduction Method",
     )
@@ -72,7 +72,7 @@ if __name__ == "__main__":
 
     imgt_version = get_imgt_version(args.imgt_version)
     data_dir = get_data_dir(args.data_dir)
-    ard = pyard.ARD(imgt_version=imgt_version, data_dir=data_dir)
+    ard = pyard.init(imgt_version=imgt_version, data_dir=data_dir)
 
     if args.version:
         version = ard.get_db_version()
@@ -82,13 +82,13 @@ if __name__ == "__main__":
 
     try:
         if args.redux_type:
-            print(ard.redux_gl(args.gl_string, args.redux_type))
+            print(ard.redux(args.gl_string, args.redux_type))
         else:
-            for redux_type in pyard.pyard.reduction_types:
+            for redux_type in pyard.misc.VALID_REDUCTION_TYPES:
                 redux_type_info = f"Reduction Method: {redux_type}"
                 print(redux_type_info)
                 print("-" * len(redux_type_info))
-                print(ard.redux_gl(args.gl_string, redux_type))
+                print(ard.redux(args.gl_string, redux_type))
     except InvalidAlleleError as e:
         print("Error:", e)
 
diff --git a/scripts/pyard-import b/scripts/pyard-import
index 50e81b9..2d7ce56 100755
--- a/scripts/pyard-import
+++ b/scripts/pyard-import
@@ -30,13 +30,13 @@ from pyard import db, data_repository
 from pyard.misc import get_data_dir
 
 
-def get_imgt_version(imgt_version):
-    if imgt_version:
-        version = imgt_version.replace(".", "")
-        if version.isdigit():
-            return version
+def get_imgt_version(version_number):
+    if version_number:
+        version_no_digit = version_number.replace(".", "")
+        if version_no_digit.isdigit():
+            return version_no_digit
         raise RuntimeError(
-            f"{imgt_version} is not a valid IMGT database version number"
+            f"{version_number} is not a valid IMGT database version number"
         )
     return "Latest"
 
@@ -133,7 +133,9 @@ if __name__ == "__main__":
         load_mac = True
 
     try:
-        ard = pyard.ARD(imgt_version=imgt_version, data_dir=data_dir, load_mac=load_mac)
+        ard = pyard.init(
+            imgt_version=imgt_version, data_dir=data_dir, load_mac=load_mac
+        )
     except ValueError as e:
         print(f"Error importing version {imgt_version}:", e)
         sys.exit(1)
diff --git a/scripts/pyard-reduce-csv b/scripts/pyard-reduce-csv
index 94acb39..fe6fa77 100755
--- a/scripts/pyard-reduce-csv
+++ b/scripts/pyard-reduce-csv
@@ -36,6 +36,7 @@ import sys
 import pandas as pd
 
 import pyard
+from pyard.db import similar_alleles
 import pyard.drbx as drbx
 from pyard.exceptions import PyArdError
 from pyard.misc import get_data_dir, get_imgt_version, download_to_file
@@ -119,7 +120,7 @@ def reduce(allele, locus, column_name):
     if should_be_reduced(allele, locus_allele):
         # print(f"reducing '{locus_allele}'")
         try:
-            reduced_allele = ard.redux_gl(locus_allele, ard_config["redux_type"])
+            reduced_allele = ard.redux(locus_allele, ard_config["redux_type"])
         except PyArdError as e:
             if verbose:
                 print(e)
@@ -246,7 +247,7 @@ if __name__ == "__main__":
 
     data_dir = get_data_dir(args.data_dir)
     imgt_version = get_imgt_version(args.imgt_version)
-    ard = pyard.ARD(imgt_version=imgt_version, data_dir=data_dir)
+    ard = pyard.init(imgt_version=imgt_version, data_dir=data_dir)
 
     # Read the Input File
     # Read only the columns to be saved.
@@ -355,9 +356,7 @@ if __name__ == "__main__":
             file=sys.stderr,
         )
         for column_name, locus_allele in failed_to_reduce_alleles:
-            similar_allele_names = pyard.db.similar_alleles(
-                ard.db_connection, locus_allele
-            )
+            similar_allele_names = similar_alleles(ard.db_connection, locus_allele)
             if similar_allele_names:
                 similar_allele_names = ",".join(
                     sorted(similar_allele_names, reverse=True)
diff --git a/setup.cfg b/setup.cfg
index 8acf945..e9ee980 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 0.9.1
+current_version = 1.0.0rc1
 commit = True
 tag = True
 
diff --git a/setup.py b/setup.py
index 2643ade..aad155a 100644
--- a/setup.py
+++ b/setup.py
@@ -39,7 +39,7 @@
 
 setup(
     name="py-ard",
-    version="0.9.1",
+    version="1.0.0rc1",
     description="ARD reduction for HLA with Python",
     long_description=readme + "\n\n" + history,
     long_description_content_type="text/markdown",


Test patch:
diff --git a/tests/environment.py b/tests/environment.py
index f778cd1..47a3e44 100644
--- a/tests/environment.py
+++ b/tests/environment.py
@@ -1,11 +1,11 @@
-from pyard import ARD
+import pyard
 
 
 def before_all(context):
-    context.ard = ARD("3440", data_dir="/tmp/py-ard")
+    context.ard = pyard.init("3440", data_dir="/tmp/py-ard")
 
     # an ard with ping set to True
     my_config = {
         "ping": True,
     }
-    context.ard_ping = ARD("3440", data_dir="/tmp/py-ard", config=my_config)
+    context.ard_ping = pyard.init("3440", data_dir="/tmp/py-ard", config=my_config)
diff --git a/tests/expected-serology.json b/tests/expected-serology.json
index 92cc7d6..4814944 100644
--- a/tests/expected-serology.json
+++ b/tests/expected-serology.json
@@ -1,5 +1,5 @@
 {
-  "redux_gl": [
+  "redux": [
     {
       "glstring": "A10",
       "ard_type": "G",
diff --git a/tests/expected.json b/tests/expected.json
index f3991f4..269f043 100644
--- a/tests/expected.json
+++ b/tests/expected.json
@@ -1,5 +1,5 @@
 {
-    "redux_gl": [
+    "redux": [
         {
             "glstring": "A*01:01:01:01+A*01:01:01:01",
             "ard_type": "G",
diff --git a/tests/features/u2_reduction.feature b/tests/features/u2_reduction.feature
index 08de6b7..ef0afcd 100644
--- a/tests/features/u2_reduction.feature
+++ b/tests/features/u2_reduction.feature
@@ -10,7 +10,7 @@ Feature: U2 Reduction
   # See Issue 121 for discussion
   # https://github.com/nmdp-bioinformatics/py-ard/issues/121
 
-  Scenario Outline: Reduce to 2 Unambiguous feilds
+  Scenario Outline: Reduce to 2 Unambiguous fields
 
     Given the allele as <Allele>
     When reducing on the <Level> level
@@ -32,10 +32,8 @@ Feature: U2 Reduction
 
 
     Examples: Ambiguous Reductions
-      | Allele                  | Level | Redux Allele |
-      | B*44:66                 | U2    | B*44:66      |
-      | B*44:270:01             | U2    | B*44:270     |
-
-      | B*44:270:01/B*44:270:02 | U2    | B*44:270     |
-
-      | B*44:66:01/B*44:66:02   | U2    | B*44:66      |
+      | Allele                      | Level | Redux Allele |
+      | B*44:66                     | U2    | B*44:66      |
+      | B*44:270:01                 | U2    | B*44:270     |
+      | B*44:270:01/B*44:270:02     | U2    | B*44:270     |
+      | B*44:15:01:01/B*44:15:01:02 | U2    | B*44:15      |
diff --git a/tests/features/who.feature b/tests/features/who.feature
index 2c9716a..8692c69 100644
--- a/tests/features/who.feature
+++ b/tests/features/who.feature
@@ -6,7 +6,7 @@ Feature: WHO expansion and Exon reduction
   `W` is an expanding type (like MAC and XX) in that finds all WHO alleles consistent with the input.
   `exon` is a reducing type, it expects input that is already exon (3field) resolution or higher (4 field) and reduces it to 3 field.
 
-  To covert 2-field/MAC/XX to exon/3-field resolution it requires calling redux_gl twice: once at W level to expand and then at exon level to reduce the output to exon/3-field
+  To covert 2-field/MAC/XX to exon/3-field resolution it requires calling redux twice: once at W level to expand and then at exon level to reduce the output to exon/3-field
 
   Scenario Outline: WHO expansion
 
diff --git a/tests/steps/redux_allele.py b/tests/steps/redux_allele.py
index d0d15e0..f1d3301 100644
--- a/tests/steps/redux_allele.py
+++ b/tests/steps/redux_allele.py
@@ -25,7 +25,7 @@ def step_impl(context, level):
 def step_impl(context, level):
     context.level = level
     try:
-        context.redux_allele = context.ard.redux_gl(context.allele, level)
+        context.redux_allele = context.ard.redux(context.allele, level)
     except PyArdError:
         context.redux_allele = "X"
 
@@ -52,13 +52,13 @@ def step_impl(context, allele):
 
 @when("expanding at the {level} level")
 def step_impl(context, level):
-    context.expanded_alleles = context.ard.redux_gl(context.allele, level)
+    context.expanded_alleles = context.ard.redux(context.allele, level)
 
 
 @when("expanding to WHO then reducing to the {level} level")
 def step_impl(context, level):
-    context.expanded_alleles = context.ard.redux_gl(
-        context.ard.redux_gl(context.allele, "W"), level
+    context.expanded_alleles = context.ard.redux(
+        context.ard.redux(context.allele, "W"), level
     )
 
 
diff --git a/tests/test_pyard.py b/tests/test_pyard.py
index 02f9e05..1af7cdd 100644
--- a/tests/test_pyard.py
+++ b/tests/test_pyard.py
@@ -32,9 +32,9 @@
 import os
 import unittest
 
-import pyard.pyard
-from pyard import ARD
+import pyard
 from pyard.exceptions import InvalidAlleleError, InvalidMACError, InvalidTypingError
+from pyard.misc import validate_reduction_type
 
 
 class TestPyArd(unittest.TestCase):
@@ -44,10 +44,7 @@ class TestPyArd(unittest.TestCase):
     @classmethod
     def setUpClass(cls) -> None:
         cls.db_version = "3440"
-        cls.ard = ARD(cls.db_version, data_dir="/tmp/py-ard")
-
-    def setUp(self):
-        self.assertIsInstance(self.ard, ARD)
+        cls.ard = pyard.init(cls.db_version, data_dir="/tmp/py-ard")
 
     def test_no_mac(self):
         self.assertEqual(self.ard.redux("A*01:01:01", "G"), "A*01:01:01G")
@@ -61,53 +58,53 @@ def test_remove_invalid(self):
         self.assertEqual(self.ard.redux("A*01:01:01", "G"), "A*01:01:01G")
 
     def test_mac(self):
-        self.assertEqual(self.ard.redux_gl("A*01:AB", "G"), "A*01:01:01G/A*01:02")
+        self.assertEqual(self.ard.redux("A*01:AB", "G"), "A*01:01:01G/A*01:02")
         self.assertEqual(
-            self.ard.redux_gl("HLA-A*01:AB", "G"), "HLA-A*01:01:01G/HLA-A*01:02"
+            self.ard.redux("HLA-A*01:AB", "G"), "HLA-A*01:01:01G/HLA-A*01:02"
         )
 
-    def test_redux_gl(self):
+    def test_redux(self):
         data_dir = os.path.dirname(__file__)
         expected_json = data_dir + "/expected.json"
         with open(expected_json) as json_data:
             expected = json.load(json_data)
-        for ex in expected["redux_gl"]:
+        for ex in expected["redux"]:
             glstring = ex["glstring"]
             ard_type = ex["ard_type"]
             expected_gl = ex["expected_gl"]
-            self.assertEqual(self.ard.redux_gl(glstring, ard_type), expected_gl)
+            self.assertEqual(self.ard.redux(glstring, ard_type), expected_gl)
 
     def test_serology(self):
         data_dir = os.path.dirname(__file__)
         expected_json = data_dir + "/expected-serology.json"
         with open(expected_json) as json_data:
             expected = json.load(json_data)
-        for ex in expected["redux_gl"]:
+        for ex in expected["redux"]:
             glstring = ex["glstring"]
             ard_type = ex["ard_type"]
             expected_gl = ex["expected_gl"]
-            self.assertEqual(self.ard.redux_gl(glstring, ard_type), expected_gl)
+            self.assertEqual(self.ard.redux(glstring, ard_type), expected_gl)
 
     def test_mac_G(self):
         self.assertEqual(self.ard.redux("A*01:01:01", "G"), "A*01:01:01G")
         self.assertEqual(
-            self.ard.redux_gl("HLA-A*01:AB", "G"), "HLA-A*01:01:01G/HLA-A*01:02"
+            self.ard.redux("HLA-A*01:AB", "G"), "HLA-A*01:01:01G/HLA-A*01:02"
         )
         with self.assertRaises(InvalidAlleleError):
-            self.ard.redux("HLA-A*01:AB", "G")
+            self.ard._redux_allele("HLA-A*01:AB", "G")
 
     def test_xx_code(self):
         expanded_string = """
         B*40:01:01G/B*40:01:03G/B*40:02:01G/B*40:03:01G/B*40:04:01G/B*40:05:01G/B*40:06:01G/B*40:07/B*40:08/B*40:09/B*40:10:01G/B*40:11:01G/B*40:12/B*40:13/B*40:14/B*40:15/B*40:16:01G/B*40:18/B*40:19/B*40:20:01G/B*40:21/B*40:22N/B*40:23/B*40:24/B*40:25/B*40:26/B*40:27/B*40:28/B*40:29/B*40:30/B*40:31/B*40:32/B*40:33/B*40:34/B*40:35/B*40:36/B*40:37/B*40:38/B*40:39/B*40:40:01G/B*40:42/B*40:43/B*40:44/B*40:45/B*40:46/B*40:47/B*40:48/B*40:49/B*40:50:01G/B*40:51/B*40:52/B*40:53/B*40:54/B*40:57/B*40:58/B*40:59/B*40:60/B*40:61/B*40:62/B*40:63/B*40:64:01G/B*40:65/B*40:66/B*40:67/B*40:68/B*40:69/B*40:70/B*40:71/B*40:72/B*40:73/B*40:74/B*40:75/B*40:76/B*40:77/B*40:78/B*40:79/B*40:80/B*40:81/B*40:82/B*40:83/B*40:84/B*40:85/B*40:86/B*40:87/B*40:88/B*40:89/B*40:90/B*40:91/B*40:92/B*40:93/B*40:94/B*40:95/B*40:96/B*40:98/B*40:99/B*40:100/B*40:101/B*40:102/B*40:103/B*40:104/B*40:105/B*40:106/B*40:107/B*40:108/B*40:109/B*40:110/B*40:111/B*40:112/B*40:113/B*40:114:01G/B*40:115/B*40:116/B*40:117/B*40:118N/B*40:119/B*40:120/B*40:121/B*40:122/B*40:123/B*40:124/B*40:125/B*40:126/B*40:127/B*40:128/B*40:129/B*40:130/B*40:131/B*40:132/B*40:133Q/B*40:134/B*40:135/B*40:136/B*40:137/B*40:138/B*40:139/B*40:140/B*40:142N/B*40:143/B*40:145/B*40:146/B*40:147/B*40:148/B*40:149/B*40:152/B*40:153/B*40:154/B*40:155:01G/B*40:156/B*40:157/B*40:158/B*40:159/B*40:160/B*40:161/B*40:162/B*40:163/B*40:164/B*40:165/B*40:166/B*40:167/B*40:168/B*40:169/B*40:170/B*40:171/B*40:172/B*40:173/B*40:174/B*40:175/B*40:177/B*40:178/B*40:180/B*40:181/B*40:182/B*40:183/B*40:184/B*40:185/B*40:186/B*40:187/B*40:188/B*40:189/B*40:190/B*40:191/B*40:192/B*40:193/B*40:194/B*40:195/B*40:196/B*40:197/B*40:198/B*40:199/B*40:200/B*40:201/B*40:202/B*40:203/B*40:204/B*40:205/B*40:206/B*40:207/B*40:208/B*40:209/B*40:210/B*40:211/B*40:212/B*40:213:01G/B*40:214/B*40:215/B*40:216N/B*40:217/B*40:218/B*40:219/B*40:220/B*40:222/B*40:223/B*40:224/B*40:225/B*40:226/B*40:227/B*40:228/B*40:230/B*40:231/B*40:232/B*40:233/B*40:234/B*40:235/B*40:237/B*40:238/B*40:239/B*40:240/B*40:242/B*40:243/B*40:244/B*40:245/B*40:246/B*40:248/B*40:249/B*40:250/B*40:251/B*40:252/B*40:253/B*40:254/B*40:255/B*40:256N/B*40:257/B*40:258/B*40:259/B*40:260/B*40:261/B*40:262/B*40:263N/B*40:265N/B*40:266/B*40:268/B*40:269/B*40:270/B*40:271/B*40:273/B*40:274/B*40:275/B*40:276/B*40:277/B*40:279/B*40:280/B*40:281/B*40:282/B*40:283/B*40:284/B*40:285/B*40:286N/B*40:287/B*40:288/B*40:289/B*40:290/B*40:291N/B*40:292/B*40:293/B*40:294/B*40:295/B*40:296/B*40:297/B*40:298/B*40:300/B*40:302/B*40:304/B*40:305/B*40:306/B*40:307/B*40:308/B*40:309/B*40:310/B*40:311/B*40:312/B*40:313/B*40:314/B*40:315/B*40:316/B*40:317/B*40:318/B*40:319/B*40:320/B*40:321/B*40:322/B*40:323/B*40:324/B*40:325/B*40:326/B*40:327/B*40:328/B*40:330/B*40:331/B*40:332/B*40:333/B*40:334/B*40:335/B*40:336/B*40:337N/B*40:339/B*40:340/B*40:341/B*40:342/B*40:343/B*40:344/B*40:345N/B*40:346/B*40:347/B*40:348/B*40:349/B*40:350/B*40:351/B*40:352/B*40:354/B*40:355/B*40:357/B*40:358/B*40:359/B*40:360/B*40:361N/B*40:362/B*40:363/B*40:364/B*40:365/B*40:366/B*40:367/B*40:368/B*40:369/B*40:370/B*40:371/B*40:372N/B*40:373/B*40:374/B*40:375/B*40:376/B*40:377/B*40:378/B*40:380/B*40:381/B*40:382/B*40:385/B*40:388/B*40:389/B*40:390/B*40:391/B*40:392/B*40:393/B*40:394/B*40:396/B*40:397/B*40:398/B*40:399N/B*40:400/B*40:401/B*40:402/B*40:403/B*40:404/B*40:407/B*40:408/B*40:409/B*40:410/B*40:411/B*40:412/B*40:413/B*40:414/B*40:415/B*40:420/B*40:421Q/B*40:422/B*40:423/B*40:424/B*40:426N/B*40:428N/B*40:429/B*40:430/B*40:432/B*40:433/B*40:434/B*40:436/B*40:437/B*40:438N/B*40:441/B*40:445/B*40:447/B*40:448/B*40:449/B*40:451/B*40:452/B*40:454/B*40:457/B*40:458/B*40:459/B*40:460/B*40:461/B*40:462/B*40:463/B*40:465/B*40:466/B*40:467/B*40:468/B*40:469/B*40:470/B*40:471/B*40:472/B*40:477/B*40:478/B*40:479/B*40:481N/B*40:482
         """.strip()
-        gl = self.ard.redux_gl("B*40:XX", "G")
+        gl = self.ard.redux("B*40:XX", "G")
         self.assertEqual(gl, expanded_string)
 
     def test_xx_code_with_prefix(self):
         expanded_string = """
         HLA-B*40:01:01G/HLA-B*40:01:03G/HLA-B*40:02:01G/HLA-B*40:03:01G/HLA-B*40:04:01G/HLA-B*40:05:01G/HLA-B*40:06:01G/HLA-B*40:07/HLA-B*40:08/HLA-B*40:09/HLA-B*40:10:01G/HLA-B*40:11:01G/HLA-B*40:12/HLA-B*40:13/HLA-B*40:14/HLA-B*40:15/HLA-B*40:16:01G/HLA-B*40:18/HLA-B*40:19/HLA-B*40:20:01G/HLA-B*40:21/HLA-B*40:22N/HLA-B*40:23/HLA-B*40:24/HLA-B*40:25/HLA-B*40:26/HLA-B*40:27/HLA-B*40:28/HLA-B*40:29/HLA-B*40:30/HLA-B*40:31/HLA-B*40:32/HLA-B*40:33/HLA-B*40:34/HLA-B*40:35/HLA-B*40:36/HLA-B*40:37/HLA-B*40:38/HLA-B*40:39/HLA-B*40:40:01G/HLA-B*40:42/HLA-B*40:43/HLA-B*40:44/HLA-B*40:45/HLA-B*40:46/HLA-B*40:47/HLA-B*40:48/HLA-B*40:49/HLA-B*40:50:01G/HLA-B*40:51/HLA-B*40:52/HLA-B*40:53/HLA-B*40:54/HLA-B*40:57/HLA-B*40:58/HLA-B*40:59/HLA-B*40:60/HLA-B*40:61/HLA-B*40:62/HLA-B*40:63/HLA-B*40:64:01G/HLA-B*40:65/HLA-B*40:66/HLA-B*40:67/HLA-B*40:68/HLA-B*40:69/HLA-B*40:70/HLA-B*40:71/HLA-B*40:72/HLA-B*40:73/HLA-B*40:74/HLA-B*40:75/HLA-B*40:76/HLA-B*40:77/HLA-B*40:78/HLA-B*40:79/HLA-B*40:80/HLA-B*40:81/HLA-B*40:82/HLA-B*40:83/HLA-B*40:84/HLA-B*40:85/HLA-B*40:86/HLA-B*40:87/HLA-B*40:88/HLA-B*40:89/HLA-B*40:90/HLA-B*40:91/HLA-B*40:92/HLA-B*40:93/HLA-B*40:94/HLA-B*40:95/HLA-B*40:96/HLA-B*40:98/HLA-B*40:99/HLA-B*40:100/HLA-B*40:101/HLA-B*40:102/HLA-B*40:103/HLA-B*40:104/HLA-B*40:105/HLA-B*40:106/HLA-B*40:107/HLA-B*40:108/HLA-B*40:109/HLA-B*40:110/HLA-B*40:111/HLA-B*40:112/HLA-B*40:113/HLA-B*40:114:01G/HLA-B*40:115/HLA-B*40:116/HLA-B*40:117/HLA-B*40:118N/HLA-B*40:119/HLA-B*40:120/HLA-B*40:121/HLA-B*40:122/HLA-B*40:123/HLA-B*40:124/HLA-B*40:125/HLA-B*40:126/HLA-B*40:127/HLA-B*40:128/HLA-B*40:129/HLA-B*40:130/HLA-B*40:131/HLA-B*40:132/HLA-B*40:133Q/HLA-B*40:134/HLA-B*40:135/HLA-B*40:136/HLA-B*40:137/HLA-B*40:138/HLA-B*40:139/HLA-B*40:140/HLA-B*40:142N/HLA-B*40:143/HLA-B*40:145/HLA-B*40:146/HLA-B*40:147/HLA-B*40:148/HLA-B*40:149/HLA-B*40:152/HLA-B*40:153/HLA-B*40:154/HLA-B*40:155:01G/HLA-B*40:156/HLA-B*40:157/HLA-B*40:158/HLA-B*40:159/HLA-B*40:160/HLA-B*40:161/HLA-B*40:162/HLA-B*40:163/HLA-B*40:164/HLA-B*40:165/HLA-B*40:166/HLA-B*40:167/HLA-B*40:168/HLA-B*40:169/HLA-B*40:170/HLA-B*40:171/HLA-B*40:172/HLA-B*40:173/HLA-B*40:174/HLA-B*40:175/HLA-B*40:177/HLA-B*40:178/HLA-B*40:180/HLA-B*40:181/HLA-B*40:182/HLA-B*40:183/HLA-B*40:184/HLA-B*40:185/HLA-B*40:186/HLA-B*40:187/HLA-B*40:188/HLA-B*40:189/HLA-B*40:190/HLA-B*40:191/HLA-B*40:192/HLA-B*40:193/HLA-B*40:194/HLA-B*40:195/HLA-B*40:196/HLA-B*40:197/HLA-B*40:198/HLA-B*40:199/HLA-B*40:200/HLA-B*40:201/HLA-B*40:202/HLA-B*40:203/HLA-B*40:204/HLA-B*40:205/HLA-B*40:206/HLA-B*40:207/HLA-B*40:208/HLA-B*40:209/HLA-B*40:210/HLA-B*40:211/HLA-B*40:212/HLA-B*40:213:01G/HLA-B*40:214/HLA-B*40:215/HLA-B*40:216N/HLA-B*40:217/HLA-B*40:218/HLA-B*40:219/HLA-B*40:220/HLA-B*40:222/HLA-B*40:223/HLA-B*40:224/HLA-B*40:225/HLA-B*40:226/HLA-B*40:227/HLA-B*40:228/HLA-B*40:230/HLA-B*40:231/HLA-B*40:232/HLA-B*40:233/HLA-B*40:234/HLA-B*40:235/HLA-B*40:237/HLA-B*40:238/HLA-B*40:239/HLA-B*40:240/HLA-B*40:242/HLA-B*40:243/HLA-B*40:244/HLA-B*40:245/HLA-B*40:246/HLA-B*40:248/HLA-B*40:249/HLA-B*40:250/HLA-B*40:251/HLA-B*40:252/HLA-B*40:253/HLA-B*40:254/HLA-B*40:255/HLA-B*40:256N/HLA-B*40:257/HLA-B*40:258/HLA-B*40:259/HLA-B*40:260/HLA-B*40:261/HLA-B*40:262/HLA-B*40:263N/HLA-B*40:265N/HLA-B*40:266/HLA-B*40:268/HLA-B*40:269/HLA-B*40:270/HLA-B*40:271/HLA-B*40:273/HLA-B*40:274/HLA-B*40:275/HLA-B*40:276/HLA-B*40:277/HLA-B*40:279/HLA-B*40:280/HLA-B*40:281/HLA-B*40:282/HLA-B*40:283/HLA-B*40:284/HLA-B*40:285/HLA-B*40:286N/HLA-B*40:287/HLA-B*40:288/HLA-B*40:289/HLA-B*40:290/HLA-B*40:291N/HLA-B*40:292/HLA-B*40:293/HLA-B*40:294/HLA-B*40:295/HLA-B*40:296/HLA-B*40:297/HLA-B*40:298/HLA-B*40:300/HLA-B*40:302/HLA-B*40:304/HLA-B*40:305/HLA-B*40:306/HLA-B*40:307/HLA-B*40:308/HLA-B*40:309/HLA-B*40:310/HLA-B*40:311/HLA-B*40:312/HLA-B*40:313/HLA-B*40:314/HLA-B*40:315/HLA-B*40:316/HLA-B*40:317/HLA-B*40:318/HLA-B*40:319/HLA-B*40:320/HLA-B*40:321/HLA-B*40:322/HLA-B*40:323/HLA-B*40:324/HLA-B*40:325/HLA-B*40:326/HLA-B*40:327/HLA-B*40:328/HLA-B*40:330/HLA-B*40:331/HLA-B*40:332/HLA-B*40:333/HLA-B*40:334/HLA-B*40:335/HLA-B*40:336/HLA-B*40:337N/HLA-B*40:339/HLA-B*40:340/HLA-B*40:341/HLA-B*40:342/HLA-B*40:343/HLA-B*40:344/HLA-B*40:345N/HLA-B*40:346/HLA-B*40:347/HLA-B*40:348/HLA-B*40:349/HLA-B*40:350/HLA-B*40:351/HLA-B*40:352/HLA-B*40:354/HLA-B*40:355/HLA-B*40:357/HLA-B*40:358/HLA-B*40:359/HLA-B*40:360/HLA-B*40:361N/HLA-B*40:362/HLA-B*40:363/HLA-B*40:364/HLA-B*40:365/HLA-B*40:366/HLA-B*40:367/HLA-B*40:368/HLA-B*40:369/HLA-B*40:370/HLA-B*40:371/HLA-B*40:372N/HLA-B*40:373/HLA-B*40:374/HLA-B*40:375/HLA-B*40:376/HLA-B*40:377/HLA-B*40:378/HLA-B*40:380/HLA-B*40:381/HLA-B*40:382/HLA-B*40:385/HLA-B*40:388/HLA-B*40:389/HLA-B*40:390/HLA-B*40:391/HLA-B*40:392/HLA-B*40:393/HLA-B*40:394/HLA-B*40:396/HLA-B*40:397/HLA-B*40:398/HLA-B*40:399N/HLA-B*40:400/HLA-B*40:401/HLA-B*40:402/HLA-B*40:403/HLA-B*40:404/HLA-B*40:407/HLA-B*40:408/HLA-B*40:409/HLA-B*40:410/HLA-B*40:411/HLA-B*40:412/HLA-B*40:413/HLA-B*40:414/HLA-B*40:415/HLA-B*40:420/HLA-B*40:421Q/HLA-B*40:422/HLA-B*40:423/HLA-B*40:424/HLA-B*40:426N/HLA-B*40:428N/HLA-B*40:429/HLA-B*40:430/HLA-B*40:432/HLA-B*40:433/HLA-B*40:434/HLA-B*40:436/HLA-B*40:437/HLA-B*40:438N/HLA-B*40:441/HLA-B*40:445/HLA-B*40:447/HLA-B*40:448/HLA-B*40:449/HLA-B*40:451/HLA-B*40:452/HLA-B*40:454/HLA-B*40:457/HLA-B*40:458/HLA-B*40:459/HLA-B*40:460/HLA-B*40:461/HLA-B*40:462/HLA-B*40:463/HLA-B*40:465/HLA-B*40:466/HLA-B*40:467/HLA-B*40:468/HLA-B*40:469/HLA-B*40:470/HLA-B*40:471/HLA-B*40:472/HLA-B*40:477/HLA-B*40:478/HLA-B*40:479/HLA-B*40:481N/HLA-B*40:482
         """.strip()
-        gl = self.ard.redux_gl("HLA-B*40:XX", "G")
+        gl = self.ard.redux("HLA-B*40:XX", "G")
         self.assertEqual(expanded_string, gl)
 
     def test_expand_mac(self):
@@ -130,35 +127,35 @@ def test_mac_toG(self):
             self.ard.mac_toG("A*01:AB")
 
     def test_redux_types(self):
-        self.assertIsNone(pyard.pyard.validate_reduction_type("G"))
-        self.assertIsNone(pyard.pyard.validate_reduction_type("lg"))
-        self.assertIsNone(pyard.pyard.validate_reduction_type("lgx"))
-        self.assertIsNone(pyard.pyard.validate_reduction_type("W"))
-        self.assertIsNone(pyard.pyard.validate_reduction_type("exon"))
+        self.assertIsNone(validate_reduction_type("G"))
+        self.assertIsNone(validate_reduction_type("lg"))
+        self.assertIsNone(validate_reduction_type("lgx"))
+        self.assertIsNone(validate_reduction_type("W"))
+        self.assertIsNone(validate_reduction_type("exon"))
         with self.assertRaises(ValueError):
-            pyard.pyard.validate_reduction_type("XX")
+            validate_reduction_type("XX")
 
     def test_empty_allele(self):
         with self.assertRaises(InvalidTypingError):
-            self.ard.redux_gl("A*", "lgx")
+            self.ard.redux("A*", "lgx")
 
     def test_fp_allele(self):
         with self.assertRaises(InvalidTypingError):
-            self.ard.redux_gl("A*0.123", "lgx")
+            self.ard.redux("A*0.123", "lgx")
 
     def test_invalid_serology(self):
         # Test that A10 works and the first one is 'A*25:01'
-        serology_a10 = self.ard.redux_gl("A10", "lgx")
+        serology_a10 = self.ard.redux("A10", "lgx")
         self.assertEqual(serology_a10.split("/")[0], "A*25:01")
         # And A100 isn't a valid typing
         with self.assertRaises(InvalidTypingError):
-            self.ard.redux_gl("A100", "lgx")
+            self.ard.redux("A100", "lgx")
 
     def test_allele_duplicated(self):
         # Make sure the reduced alleles are unique
         # https://github.com/nmdp-bioinformatics/py-ard/issues/135
         allele_code = "C*02:ACMGS"
-        allele_code_rx = self.ard.redux_gl(allele_code, "lgx")
+        allele_code_rx = self.ard.redux(allele_code, "lgx")
         self.assertEqual(allele_code_rx, "C*02:02")
 
     def test_imgt_db_version(self):
@@ -166,6 +163,24 @@ def test_imgt_db_version(self):
 
     def test_xx_codes_broad_split(self):
         self.assertFalse(
-            "DQB1*06" in self.ard.redux_gl("DQB1*05:XX", "lgx"),
+            "DQB1*06" in self.ard.redux("DQB1*05:XX", "lgx"),
             "The split shouldn't include other splits",
         )
+
+    def test_cache_info(self):
+        # validate the default cache size
+        self.assertEqual(
+            self.ard._redux_allele.cache_info().maxsize, pyard.misc.DEFAULT_CACHE_SIZE
+        )
+        self.assertEqual(
+            self.ard.redux.cache_info().maxsize, pyard.misc.DEFAULT_CACHE_SIZE
+        )
+        # validate you can change the cache size
+        higher_cache_size = 5_000_000
+        another_ard = pyard.init(
+            self.db_version, data_dir="/tmp/py-ard", cache_size=higher_cache_size
+        )
+        self.assertEqual(
+            another_ard._redux_allele.cache_info().maxsize, higher_cache_size
+        )
+        self.assertEqual(another_ard.redux.cache_info().maxsize, higher_cache_size)