Repository: networktocode/network-importer (language: python)

Issue:
Add nautobot integration tests
- Adds integration tests for Nautobot


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 398c7f28..c6b6452c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -149,3 +149,4 @@ dmypy.json
 .pyre/
 
 *.env
+.vscode
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 72a44138..5109a455 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -54,6 +54,24 @@ jobs:
         - "invoke pylint"
       python: 3.7
 
+    # - stage: "integration"
+    #   name: "Nautobot v1.0.1"
+    #   before_install: >
+    #     echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin || travis_terminate 1
+    #   before_script:
+    #     - "pip install --upgrade pip wheel"
+    #     - "pip install invoke toml poetry pynautobot ansible==2.10.7"
+    #     - "ansible-galaxy collection install networktocode.nautobot"
+    #     - "poetry config virtualenvs.create false"
+    #     - "poetry config installer.parallel false"
+    #     - "poetry install"
+    #   script:
+    #     - "invoke nautobot-integration-tests"
+    #   after_script:
+    #     - "cd /tmp && docker-compose logs"
+    #   env: "NAUTOBOT_VERSION=1.0.1"
+    #   python: 3.7
+
     - stage: "integration"
       name: "Netbox v2.10"
       before_install: >
diff --git a/examples/multi_site_02/ansible.cfg b/examples/multi_site_02/ansible.cfg
index 1a5f421a..eb3bc696 100644
--- a/examples/multi_site_02/ansible.cfg
+++ b/examples/multi_site_02/ansible.cfg
@@ -1,4 +1,5 @@
 [defaults]
+forks=1
 host_key_checking = False
 interpreter_python = auto_silent
 inventory = inventory.ini
\ No newline at end of file
diff --git a/examples/multi_site_02/nautobot_setup.py b/examples/multi_site_02/nautobot_setup.py
new file mode 100644
index 00000000..434055d9
--- /dev/null
+++ b/examples/multi_site_02/nautobot_setup.py
@@ -0,0 +1,181 @@
+"""Script to add components for Nautobot."""
+import os
+import urllib3
+
+import pynautobot
+
+# pylint: disable=too-many-locals
+# pylint: disable=too-many-branches
+
+PLATFORMS = [
+    {"name": "junos", "manufacturer": "juniper"},
+    {"name": "iosxr", "manufacturer": "cisco"},
+    {"name": "ios", "manufacturer": "cisco"},
+    {"name": "nxos", "manufacturer": "cisco"},
+    {"name": "eos", "manufacturer": "arista"},
+]
+
+DEVICE_LIST = [
+    {"name": "nyc-bb-01", "role": "router", "type": "ios"},
+    {"name": "nyc-rtr-01", "role": "router", "type": "ios"},
+    {"name": "nyc-rtr-02", "role": "router", "type": "ios"},
+    {"name": "nyc-spine-01", "role": "spine", "type": "nxos"},
+    {"name": "nyc-spine-02", "role": "spine", "type": "nxos"},
+    {"name": "nyc-leaf-01", "role": "leaf", "type": "nxos"},
+    {"name": "hou-bb-01", "role": "router", "type": "ios"},
+    {"name": "hou-rtr-01", "role": "router", "type": "junos"},
+    {"name": "hou-rtr-02", "role": "router", "type": "junos"},
+    {"name": "hou-spine-01", "role": "spine", "type": "eos"},
+    {"name": "hou-spine-02", "role": "spine", "type": "eos"},
+    {"name": "hou-leaf-01", "role": "leaf", "type": "eos"},
+    {"name": "hou-leaf-02", "role": "leaf", "type": "eos"},
+    {"name": "sjc-bb-01", "role": "router", "type": "ios"},
+    {"name": "sjc-rtr-01", "role": "router", "type": "iosxr"},
+    {"name": "sjc-rtr-02", "role": "router", "type": "iosxr"},
+    {"name": "sjc-leaf-01", "role": "leaf", "type": "eos"},
+]
+
+SITE_LIST = ["hou", "nyc", "sjc"]
+
+DEVICE_ROLES = ["spine", "leaf", "router"]
+
+
+def get_or_create(object_endpoint, search_key, search_term, **kwargs):
+    """Get or create object with pynautobot.
+
+    Args:
+        object_endpoint (pynautobot Endpoint): Endpoint to make get and post requests to
+        search_key (str): Key to use within the get request, such as name or model
+        search_term (str): What is being searched for
+
+    Returns:
+        obj (pynautobot object), created (bool): Object being created, and whether or not it was created
+    """
+    created = False
+    search = {search_key: search_term}
+    if kwargs.get("just_create_device"):
+        try:
+            print("Just creating device")
+            obj = object_endpoint.create(**search, **kwargs)
+            created = True
+            return obj, created
+        except:  # pylint: disable=bare-except # noqa: E722, E261
+            return None, True
+
+    obj = object_endpoint.get(**search)
+    if obj is None:
+        obj = object_endpoint.create(**search, **kwargs)
+        created = True
+
+    return obj, created
+
+
+def main():
+    """Main code execution block."""
+    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+    nautobot = pynautobot.api(url=os.getenv("NAUTOBOT_ADDRESS"), token=os.getenv("NAUTOBOT_TOKEN"))
+    nautobot.http_session.verify = False
+
+    # Create region
+    for region in ["ni_multi_site_02"]:
+        print(f"Checking on region: {region}")
+        _, created = get_or_create(
+            object_endpoint=nautobot.dcim.regions, search_key="name", search_term=region, slug=region.lower()
+        )
+
+        if created:
+            print(f"Created region: {region}")
+        else:
+            print(f"Region already exists: {region}")
+
+    mfg_map = dict()
+    device_type_map = dict()
+
+    for item in PLATFORMS:
+        print(f"Checking on platform: {item}")
+        mfg, created = get_or_create(
+            object_endpoint=nautobot.dcim.manufacturers,
+            search_key="slug",
+            search_term=item["manufacturer"],
+            name=item["manufacturer"].capitalize(),
+            slug=item["manufacturer"].lower(),
+        )
+
+        mfg_map[item["manufacturer"]]: mfg
+        if created:
+            print(f"Created manufacturer: {item['manufacturer']}")
+        else:
+            print(f"Manufacturer already created: {item['manufacturer']}")
+
+        # Create device type if not already created
+        device_type, created = get_or_create(
+            object_endpoint=nautobot.dcim.device_types,
+            search_key="model",
+            search_term=item["name"],
+            slug=item["name"],
+            manufacturer=mfg.id,
+        )
+
+        device_type_map[item["name"]] = device_type
+        if created:
+            print(f"Create Device Type: {item['name']}")
+        else:
+            print(f"Device Type Already Created: {item['name']}")
+
+    device_role_map = dict()
+
+    # Create device role
+    for dev_role in DEVICE_ROLES:
+        print(f"Checking on device role: {dev_role}")
+        device_role_obj, created = get_or_create(
+            object_endpoint=nautobot.dcim.device_roles, search_key="name", search_term=dev_role, slug=dev_role,
+        )
+
+        if created:
+            print(f"Created device role: {dev_role}")
+        else:
+            print(f"Device role already created: {dev_role}")
+
+        device_role_map[dev_role] = device_role_obj
+
+    # Get all of the sites
+    site_map = dict()
+
+    # Iterate over the sites
+    for site in SITE_LIST:
+        print(f"Checking on site: {site}")
+        # Check if the device is created
+        site_obj, created = get_or_create(
+            object_endpoint=nautobot.dcim.sites, search_key="name", search_term=site, slug=site.lower(), status="Active"
+        )
+
+        if created:
+            print(f"Created site: {site}")
+        else:
+            print(f"Site already created: {site}")
+
+        site_map[site] = site_obj
+
+    # Create Devices
+    for dev in DEVICE_LIST:
+        print(f"Creating device: {dev}")
+        _, created = get_or_create(
+            object_endpoint=nautobot.dcim.devices,
+            search_key="name",
+            search_term=dev["name"],
+            slug=dev["name"].lower(),
+            status="Active",
+            site=site_map["ni_spine_leaf_01"].id,
+            device_type=device_type_map[dev["type"]].id,
+            device_role=device_role_map[dev["role"]].id,
+            just_create_device=True,
+        )
+
+        if created:
+            print(f"Created device: {dev['name']}")
+        else:
+            print(f"Device already created: {dev['name']}")
+
+
+if __name__ == "__main__":
+    main()
diff --git a/examples/multi_site_02/network_importer.toml b/examples/multi_site_02/network_importer.toml
index 827a994e..72e5925d 100644
--- a/examples/multi_site_02/network_importer.toml
+++ b/examples/multi_site_02/network_importer.toml
@@ -6,9 +6,11 @@ import_prefixes = true
 import_cabling = "config"
 import_vlans = "config"
 
-nbr_workers = 1
+nbr_workers = 5
 
-[inventory]
+backend = "netbox"
+
+[inventory.settings]
 filter="region=ni_multi_site_02"
 
 [logs]
diff --git a/examples/multi_site_02/network_importer_nautobot.toml b/examples/multi_site_02/network_importer_nautobot.toml
new file mode 100644
index 00000000..65e3fb47
--- /dev/null
+++ b/examples/multi_site_02/network_importer_nautobot.toml
@@ -0,0 +1,18 @@
+
+
+[main]
+import_ips = true
+import_prefixes = true
+import_cabling = "config"
+import_vlans = "config"
+
+nbr_workers = 5
+
+backend = "nautobot"
+
+[inventory.settings]
+filter="region=ni_multi_site_02"
+
+[logs]
+level = "debug"
+
diff --git a/examples/multi_site_02/pb.nautobot_setup.yaml b/examples/multi_site_02/pb.nautobot_setup.yaml
new file mode 100644
index 00000000..3248e92d
--- /dev/null
+++ b/examples/multi_site_02/pb.nautobot_setup.yaml
@@ -0,0 +1,120 @@
+---
+- name: "CREATE ALL OBJECTS REQUIRED FOR THE NETWORK IMPORTER IN NAUTOBOT"
+  hosts: ["all"]
+  gather_facts: false
+  connection: "local"
+  vars:
+    device_roles: ["spine", "leaf", "router"]
+    platforms:
+      - {name: "junos", manufacturer: "juniper"}
+      - {name: "iosxr", manufacturer: "cisco"}
+      - {name: "ios", manufacturer: "cisco"}
+      - {name: "nxos", manufacturer: "cisco"}
+      - {name: "eos", manufacturer: "arista"}
+    site_prefix: "ni_02"
+    region: "ni_multi_site_02"
+    sites: ["hou", "nyc", "sjc"]
+    nautobot_address: "{{ lookup('env','NAUTOBOT_ADDRESS') }}"
+    nautobot_token: "{{ lookup('env','NAUTOBOT_TOKEN') }}"
+
+  tasks:
+    - name: "DELEGATE TO LOCALHOST BLOCK"
+      delegate_to: "localhost"
+      block:
+        - name: "00 DEBUG VARIABLES"
+          debug:
+            msg:
+              - "Nautobot URL: {{ nautobot_address }}"
+
+        - name: "01 CREATE REGION IN NAUTOBOT"
+          networktocode.nautobot.region:
+            url: "{{ nautobot_address }}"
+            token: "{{ nautobot_token }}"
+            data:
+              name: "{{ region }}"
+            state: "present"
+            validate_certs: False
+          run_once: true
+          tags: ["region"]
+          retries: 5
+          delay: 3
+
+        - name: "05 CREATE MANUFACTURER IN NAUTOBOT"
+          networktocode.nautobot.manufacturer:
+            url: "{{ nautobot_address }}"
+            token: "{{ nautobot_token }}"
+            data:
+              name: "{{ item.manufacturer }}"
+            validate_certs: False
+            state: "present"
+          loop: "{{ platforms }}"
+          run_once: true
+          tags: ["manufacturer", "device_type"]
+          retries: 5
+          delay: 3
+
+        - name: "10 CREATE DEVICE TYPE IN NAUTOBOT"
+          networktocode.nautobot.device_type:
+            url: "{{ nautobot_address }}"
+            token: "{{ nautobot_token }}"
+            data:
+              slug: "{{ item.name }}"
+              model: "{{ item.name }}"
+              manufacturer: "{{ item.manufacturer }}"
+              part_number: "{{ item.name }}"
+              u_height: 1
+              is_full_depth: false
+            state: "present"
+            validate_certs: False
+          loop: "{{ platforms }}"
+          run_once: true
+          tags: ["device_type"]
+          retries: 5
+          delay: 3
+
+        - name: "15 CREATE DEVICE ROLE IN NAUTOBOT"
+          networktocode.nautobot.device_role:
+            url: "{{ nautobot_address }}"
+            token: "{{ nautobot_token }}"
+            data:
+              name: "{{ item }}"
+              color: "0000FF"
+            state: "present"
+            validate_certs: False
+          loop: "{{ device_roles }}"
+          run_once: true
+          tags: ["device_role"]
+          retries: 5
+          delay: 3
+
+        - name: "20 CREATE SITE IN NAUTOBOT"
+          networktocode.nautobot.site:
+            url: "{{ nautobot_address }}"
+            token: "{{ nautobot_token }}"
+            data:
+              name: "{{ site_prefix}}_{{item }}"
+              region: "{{ region }}"
+              status: "Active"
+            state: "present"
+            validate_certs: False
+          loop: "{{ sites }}"
+          run_once: true
+          tags: ["device_site"]
+          retries: 5
+          delay: 3
+
+        - name: "25 CREATE DEVICES IN NAUTOBOT"
+          networktocode.nautobot.device:
+            url: "{{ nautobot_address }}"
+            token: "{{ nautobot_token }}"
+            data:
+              name: "{{ inventory_hostname }}"
+              device_type: "{{ platform }}"
+              device_role: "{{ role }}"
+              site: "{{ site_prefix}}_{{site }}"
+              status: "Active"
+            state: "present"
+            validate_certs: False
+          tags: ["device"]
+          retries: 5
+          delay: 3
diff --git a/examples/spine_leaf_01/ansible.cfg b/examples/spine_leaf_01/ansible.cfg
index 1a5f421a..eb3bc696 100644
--- a/examples/spine_leaf_01/ansible.cfg
+++ b/examples/spine_leaf_01/ansible.cfg
@@ -1,4 +1,5 @@
 [defaults]
+forks=1
 host_key_checking = False
 interpreter_python = auto_silent
 inventory = inventory.ini
\ No newline at end of file
diff --git a/examples/spine_leaf_01/nautobot_setup.py b/examples/spine_leaf_01/nautobot_setup.py
new file mode 100644
index 00000000..60c9beb8
--- /dev/null
+++ b/examples/spine_leaf_01/nautobot_setup.py
@@ -0,0 +1,168 @@
+"""Script to add components for Nautobot."""
+import os
+import urllib3
+
+import pynautobot
+
+# pylint: disable=too-many-locals
+# pylint: disable=too-many-branches
+
+PLATFORMS = [
+    {"name": "junos", "manufacturer": "juniper"},
+    {"name": "iosxr", "manufacturer": "cisco"},
+    {"name": "eos", "manufacturer": "arista"},
+]
+
+DEVICE_LIST = [
+    {"name": "amarillo", "role": "spine", "type": "junos"},
+    {"name": "austin", "role": "spine", "type": "iosxr"},
+    {"name": "san-antonio", "role": "spine", "type": "iosxr"},
+    {"name": "el-paso", "role": "spine", "type": "iosxr"},
+    {"name": "dallas", "role": "leaf", "type": "iosxr"},
+    {"name": "houston", "role": "leaf", "type": "eos"},
+]
+
+SITE_LIST = ["ni_spine_leaf_01"]
+
+DEVICE_ROLES = ["spine", "leaf"]
+
+
+def get_or_create(object_endpoint, search_key, search_term, **kwargs):
+    """Get or create object with pynautobot.
+
+    Args:
+        object_endpoint (pynautobot Endpoint): Endpoint to make get and post requests to
+        search_key (str): Key to use within the get request, such as name or model
+        search_term (str): What is being searched for
+
+    Returns:
+        obj (pynautobot object), created (bool): Object being created, and whether or not it was created
+    """
+    created = False
+    search = {search_key: search_term}
+    if kwargs.get("just_create_device"):
+        try:
+            print("Just creating device")
+            obj = object_endpoint.create(**search, **kwargs)
+            created = True
+            return obj, created
+        except:  # pylint: disable=bare-except # noqa: E722, E261
+            return None, True
+
+    obj = object_endpoint.get(**search)
+    if obj is None:
+        obj = object_endpoint.create(**search, **kwargs)
+        created = True
+
+    return obj, created
+
+
+def main():
+    """Main code execution block."""
+    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+    nautobot = pynautobot.api(url=os.getenv("NAUTOBOT_ADDRESS"), token=os.getenv("NAUTOBOT_TOKEN"))
+    nautobot.http_session.verify = False
+
+    # Create region
+    for region in ["ni_multi_site_02"]:
+        print(f"Checking on region: {region}")
+        _, created = get_or_create(
+            object_endpoint=nautobot.dcim.regions, search_key="name", search_term=region, slug=region.lower()
+        )
+
+        if created:
+            print(f"Created region: {region}")
+        else:
+            print(f"Region already exists: {region}")
+
+    mfg_map = dict()
+    device_type_map = dict()
+
+    for item in PLATFORMS:
+        print(f"Checking on platform: {item}")
+        mfg, created = get_or_create(
+            object_endpoint=nautobot.dcim.manufacturers,
+            search_key="slug",
+            search_term=item["manufacturer"],
+            name=item["manufacturer"].capitalize(),
+            slug=item["manufacturer"].lower(),
+        )
+
+        mfg_map[item["manufacturer"]]: mfg
+        if created:
+            print(f"Created manufacturer: {item['manufacturer']}")
+        else:
+            print(f"Manufacturer already created: {item['manufacturer']}")
+
+        # Create device type if not already created
+        device_type, created = get_or_create(
+            object_endpoint=nautobot.dcim.device_types,
+            search_key="model",
+            search_term=item["name"],
+            slug=item["name"],
+            manufacturer=mfg.id,
+        )
+
+        device_type_map[item["name"]] = device_type
+        if created:
+            print(f"Create Device Type: {item['name']}")
+        else:
+            print(f"Device Type Already Created: {item['name']}")
+
+    device_role_map = dict()
+
+    # Create device role
+    for dev_role in DEVICE_ROLES:
+        print(f"Checking on device role: {dev_role}")
+        device_role_obj, created = get_or_create(
+            object_endpoint=nautobot.dcim.device_roles, search_key="name", search_term=dev_role, slug=dev_role,
+        )
+
+        if created:
+            print(f"Created device role: {dev_role}")
+        else:
+            print(f"Device role already created: {dev_role}")
+
+        device_role_map[dev_role] = device_role_obj
+
+    # Get all of the sites
+    site_map = dict()
+
+    # Iterate over the sites
+    for site in SITE_LIST:
+        print(f"Checking on site: {site}")
+        # Check if the device is created
+        site_obj, created = get_or_create(
+            object_endpoint=nautobot.dcim.sites, search_key="name", search_term=site, slug=site.lower(), status="Active"
+        )
+
+        if created:
+            print(f"Created site: {site}")
+        else:
+            print(f"Site already created: {site}")
+
+        site_map[site] = site_obj
+
+    # Create Devices
+    for dev in DEVICE_LIST:
+        print(f"Creating device: {dev}")
+        _, created = get_or_create(
+            object_endpoint=nautobot.dcim.devices,
+            search_key="name",
+            search_term=dev["name"],
+            slug=dev["name"].lower(),
+            status="Active",
+            site=site_map["ni_spine_leaf_01"].id,
+            device_type=device_type_map[dev["type"]].id,
+            device_role=device_role_map[dev["role"]].id,
+            just_create_device=True,
+        )
+
+        if created:
+            print(f"Created device: {dev['name']}")
+        else:
+            print(f"Device already created: {dev['name']}")
+
+
+if __name__ == "__main__":
+    main()
diff --git a/examples/spine_leaf_01/network_importer.toml b/examples/spine_leaf_01/network_importer.toml
index af9d77d2..2cd0dcac 100644
--- a/examples/spine_leaf_01/network_importer.toml
+++ b/examples/spine_leaf_01/network_importer.toml
@@ -6,7 +6,9 @@ import_prefixes = true
 import_cabling = "config"
 import_vlans = "config"
 
-[inventory]
+backend = "netbox"
+
+[inventory.settings]
 filter = "site=ni_spine_leaf_01"
 
 [logs]
diff --git a/examples/spine_leaf_01/network_importer_nautobot.toml b/examples/spine_leaf_01/network_importer_nautobot.toml
new file mode 100644
index 00000000..54f538a5
--- /dev/null
+++ b/examples/spine_leaf_01/network_importer_nautobot.toml
@@ -0,0 +1,15 @@
+
+
+[main]
+import_ips = true
+import_prefixes = true
+import_cabling = "config"
+import_vlans = "config"
+
+backend = "nautobot"
+
+[inventory.settings]
+filter = "site=ni_spine_leaf_01"
+
+[logs]
+level = "info"
diff --git a/examples/spine_leaf_01/pb.nautobot_setup.yaml b/examples/spine_leaf_01/pb.nautobot_setup.yaml
new file mode 100644
index 00000000..3cab1eb4
--- /dev/null
+++ b/examples/spine_leaf_01/pb.nautobot_setup.yaml
@@ -0,0 +1,88 @@
+---
+- name: "CREATE ALL OBJECTS REQUIRED FOR THE NETWORK IMPORTER IN NAUTOBOT"
+  hosts: ["all"]
+  gather_facts: false
+  connection: "local"
+  vars:
+    device_roles: ["spine", "leaf"]
+    platforms:
+      - {name: "junos", manufacturer: "juniper"}
+      - {name: "iosxr", manufacturer: "cisco"}
+      - {name: "eos", manufacturer: "arista"}
+    site: "ni_spine_leaf_01"
+    nautobot_address: "{{ lookup('env','NAUTOBOT_ADDRESS') }}"
+    nautobot_token: "{{ lookup('env','NAUTOBOT_TOKEN') }}"
+
+  tasks:
+    - name: "00 DEBUG VARIABLES"
+      debug:
+        msg:
+          - "Nautobot URL: {{ nautobot_address }}"
+
+    - name: "05 CREATE MANUFACTURER IN NAUTOBOT"
+      networktocode.nautobot.manufacturer:
+        url: "{{ nautobot_address }}"
+        token: "{{ nautobot_token }}"
+        data:
+          name: "{{ item.manufacturer }}"
+        validate_certs: False
+        state: "present"
+      loop: "{{ platforms }}"
+      run_once: true
+      tags: ["manufacturer", "device_type"]
+
+    - name: "10 CREATE DEVICE TYPE IN NAUTOBOT"
+      networktocode.nautobot.device_type:
+        url: "{{ nautobot_address }}"
+        token: "{{ nautobot_token }}"
+        data:
+          slug: "{{ item.name }}"
+          model: "{{ item.name }}"
+          manufacturer: "{{ item.manufacturer }}"
+          part_number: "{{ item.name }}"
+          u_height: 1
+          is_full_depth: false
+        validate_certs: False
+        state: "present"
+      loop: "{{ platforms }}"
+      run_once: true
+      tags: ["device_type"]
+
+    - name: "15 CREATE DEVICE ROLE IN NAUTOBOT"
+      networktocode.nautobot.device_role:
+        url: "{{ nautobot_address }}"
+        token: "{{ nautobot_token }}"
+        data:
+          name: "{{ item }}"
+          color: "0000FF"
+        validate_certs: False
+        state: "present"
+      loop: "{{ device_roles }}"
+      run_once: true
+      tags: ["device_role"]
+
+    - name: "20 CREATE SITE IN NAUTOBOT"
+      networktocode.nautobot.site:
+        url: "{{ nautobot_address }}"
+        token: "{{ nautobot_token }}"
+        data:
+          name: "{{ site }}"
+          status: "Active"
+        validate_certs: False
+        state: "present"
+      run_once: true
+      tags: ["device_site"]
+
+    - name: "25 CREATE DEVICES IN NAUTOBOT"
+      networktocode.nautobot.device:
+        url: "{{ nautobot_address }}"
+        token: "{{ nautobot_token }}"
+        data:
+          name: "{{ inventory_hostname }}"
+          device_type: "{{ platform }}"
+          device_role: "{{ role }}"
+          site: "{{ site }}"
+          status: "Active"
+        validate_certs: False
+        state: "present"
+      tags: ["device"]
diff --git a/network_importer.toml b/network_importer.toml
new file mode 100644
index 00000000..0ada7ddc
--- /dev/null
+++ b/network_importer.toml
@@ -0,0 +1,107 @@
+[main]
+inventory_source = "nautobot"
+import_ips = true 
+import_prefixes = false
+import_intf_status = false
+
+# Vlans (name, vlan-id) can be imported from the configuration, from the CLI or both 
+# - "config" will import the vlans just from the configuration
+# - "cli" will import the vlans from the cli using the action `get_vlans`
+# - "true" will import vlans from both cli and config
+# - no or false will not import any vlans
+# The association between interface and vlans will always be derived from the configuration.
+import_vlans = "cli"         # Valid options are ["cli", "config", "no", true, false]
+
+# Cabling can be imported from LLDP, CDP or the configuration (for some point to point links)
+# - "lldp" or "cdp" will import the vlans from the cli using the action `get_neighbors`
+# - "config" will import the neighbors from the configuration (for point to point links)
+# - "true" will import neighbors from both cli and config 
+# - no or false will not import any neighbors
+import_cabling = "cdp"         # Valid options are ["lldp", "cdp", "config", "no", true, false]
+excluded_platforms_cabling = ["cisco_asa"]
+
+# Number of Nornir tasks to execute at the same tim
+nbr_workers= 25
+
+# Directory where the configuration can be find, organized in Batfish format
+configs_directory = "configs"
+
+# [netbox]
+# address = "https://nautobot8"                   # Alternative Env Variable : NETBOX_ADDRESS
+# token = "6fd167451be23bc5104bc07995fa9f4185c33d48"  # Alternative Env Variable : NETBOX_TOKEN
+# verify_ssl = false                                   # Alternative Env Variable : NETBOX_VERIFY_SSL
+
+# # Define a list of supported platform, 
+# # if defined all devices without platform or with a different platforms will be removed from the inventory
+# supported_platforms = [ "cisco_ios", "cisco_nxos" ]
+
+# # Settings for applying diffsync flags to diffsync model objects, in order to alter 
+# # the underlying sync behaviour. The model_flag is applied to any objects that have a 
+# # tag assigned within model_flag_tags. Further details on model_flags can be found 
+# # at: https://github.com/networktocode/diffsync/blob/269df51ce248beaef17d72374e96d19e6df95a13/diffsync/enum.py
+# model_flag_tags = ["your_tag"]
+# model_flag = 1 # flag enum int() representation
+
+[batfish]
+address = "dockerrunner"   # Alternative Env Variable : BATFISH_ADDRESS
+network_name = "network-importer"
+snapshot_name = "latest"
+port_v1 = 9997
+port_v2 = 9996
+use_ssl = false
+
+[network]
+login = "cisco"          # Alternative Env Variable : NETWORK_DEVICE_LOGIN
+password = "Cisco123!"       # Alternative Env Variable : NETWORK_DEVICE_PWD
+enable = true               # Alternative Env Variable : NETWORK_DEVICE_ENABLE
+
+# Connection parameters for Netmiko 
+global_delay_factor = 5
+banner_timeout = 15
+conn_timeout = 5
+
+# List of valid FQDN that can be found in the network,
+# The FQDNs in this list will be automatically removed from all neighbors discovered from LLDP/CDP
+fqdns = [ ]
+
+[inventory]
+# The default method is to use the primary IP defined in NetBox.
+# As an alternative it's possible to use the name of the device and provide your own FQDN.
+use_primary_ip = false # (default: true)
+fqdn = "joshv.com"
+
+# Optional filter to limit the scope of the inventory, takes a comma separated string of key value pair"
+filter = "device=sw01" # ,site=YYY,status=active"    # Alternative Env Variable : INVENTORY_FILTER
+
+# Configure what Inventory will be loaded bu the network importer.
+inventory_class = "network_importer.inventory.NetboxInventory"
+
+[nautobot]
+address = "https://nautobot8:443"                   # Alternative Env Variable : NETBOX_ADDRESS
+token = "6fd167451be23bc5104bc07995fa9f4185c33d48"  # Alternative Env Variable : NETBOX_TOKEN
+verify_ssl = false                                   # Alternative Env Variable : NETBOX_VERIFY_SSL
+
+# Define a list of supported platform, 
+# if defined all devices without platform or with a different platforms will be removed from the inventory
+supported_platforms = [ "cisco_ios", "cisco_nxos" ]
+
+# Settings for applying diffsync flags to diffsync model objects, in order to alter 
+# the underlying sync behaviour. The model_flag is applied to any objects that have a 
+# tag assigned within model_flag_tags. Further details on model_flags can be found 
+# at: https://github.com/networktocode/diffsync/blob/269df51ce248beaef17d72374e96d19e6df95a13/diffsync/enum.py
+model_flag_tags = ["your_tag"]
+model_flag = 1 # flag enum int() representation
+
+[netbox]
+address = "https://nautobot8"
+token = "6fd167451be23bc5104bc07995fa9f4185c33d48"  # Alternative Env Variable : NETBOX_TOKEN
+verify_ssl = false                                   # Alternative Env Variable : NETBOX_VERIFY_SSL
+
+# Define a list of supported platform, 
+# if defined all devices without platform or with a different platforms will be removed from the inventory
+supported_platforms = [ "cisco_ios", "cisco_nxos" ]
+
+# Settings for applying diffsync flags to diffsync model objects, in order to alter 
+# the underlying sync behaviour. The model_flag is applied to any objects that have a 
+# tag assigned within model_flag_tags. Further details on model_flags can be found 
+# at: https://github.com/networktocode/diffsync/blob/269df51ce248beaef17d72374e96d19e6df95a13/diffsync/enum.py
diff --git a/network_importer/adapters/base.py b/network_importer/adapters/base.py
index 79f2d71c..f17961fb 100644
--- a/network_importer/adapters/base.py
+++ b/network_importer/adapters/base.py
@@ -1,17 +1,4 @@
-"""BaseAdapter for the network importer.
-
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""BaseAdapter for the network importer."""
 from diffsync import DiffSync
 from network_importer.models import Site, Device, Interface, IPAddress, Cable, Vlan, Prefix
 
@@ -27,16 +14,24 @@ class BaseAdapter(DiffSync):
     vlan = Vlan
     prefix = Prefix
 
-    def __init__(self, nornir, config):
-        """Initialize the base adapter and store the nornir object locally."""
+    settings_class = None
+    settings = None
+
+    def __init__(self, nornir, settings):
+        """Initialize the base adapter and store the Nornir object locally."""
         super().__init__()
         self.nornir = nornir
-        self.config = self._validate_config(config)
+        self.settings = self._validate_settings(settings)
+
+    def _validate_settings(self, settings):
+        """Load and validate the configuration based on the settings_class."""
+        if self.settings_class:
+            if settings and isinstance(dict, settings):
+                return self.settings_class(**settings)  # pylint: disable=not-callable
+
+            return self.settings_class()  # pylint: disable=not-callable
 
-    @classmethod
-    def _validate_config(cls, config):
-        """Placeholder method that can be reimplemented by each adapter to validate if its config is valid."""
-        return config
+        return settings
 
     def load(self):
         """Load the local cache with data from the remove system."""
diff --git a/network_importer/adapters/nautobot_api/adapter.py b/network_importer/adapters/nautobot_api/adapter.py
new file mode 100644
index 00000000..a13e08b3
--- /dev/null
+++ b/network_importer/adapters/nautobot_api/adapter.py
@@ -0,0 +1,436 @@
+"""NautobotAPIAdapter class."""
+import logging
+import warnings
+
+import pynautobot
+from packaging.version import Version, InvalidVersion
+
+from diffsync.exceptions import ObjectAlreadyExists, ObjectNotFound
+import network_importer.config as config  # pylint: disable=import-error
+from network_importer.adapters.base import BaseAdapter  # pylint: disable=import-error
+from network_importer.adapters.nautobot_api.models import (  # pylint: disable=import-error
+    NautobotSite,
+    NautobotDevice,
+    NautobotInterface,
+    NautobotIPAddress,
+    NautobotCable,
+    NautobotPrefix,
+    NautobotVlan,
+)
+from network_importer.adapters.nautobot_api.tasks import query_device_info_from_nautobot
+from network_importer.adapters.nautobot_api.settings import InventorySettings, AdapterSettings
+
+warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+with warnings.catch_warnings():
+    warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+LOGGER = logging.getLogger("network-importer")
+
+
+class NautobotAPIAdapter(BaseAdapter):
+    """Adapter to import Data from a Nautobot Server over its API."""
+
+    site = NautobotSite
+    device = NautobotDevice
+    interface = NautobotInterface
+    ip_address = NautobotIPAddress
+    cable = NautobotCable
+    vlan = NautobotVlan
+    prefix = NautobotPrefix
+
+    top_level = ["site", "device", "cable"]
+
+    nautobot = None
+    nautobot_version = None
+
+    settings_class = AdapterSettings
+
+    type = "Nautobot"
+
+    query_device_info_from_nautobot = query_device_info_from_nautobot
+
+    def _is_tag_present(self, nautobot_obj):
+        """Find if tag is present for a given object."""
+        if isinstance(nautobot_obj, dict) and not nautobot_obj.get("tags", None):  # pylint: disable=no-else-return
+            return False
+        elif not isinstance(nautobot_obj, dict):  # pylint: disable=no-else-return
+            try:
+                nautobot_obj["tags"]
+            except AttributeError:
+                return False
+        elif not nautobot_obj["tags"]:
+            return False
+
+        for tag in self.settings.model_flag_tags:
+            if tag in nautobot_obj["tags"]:
+                LOGGER.debug(
+                    "Tag (%s) found for object %s. Marked for diffsync flag assignment.", tag, nautobot_obj,
+                )
+                return True
+        return False
+
+    def apply_model_flag(self, diffsync_obj, nautobot_obj):
+        """Helper function for DiffSync Flag assignment."""
+        model_flag = self.settings.model_flag
+
+        if model_flag and self._is_tag_present(nautobot_obj):
+            LOGGER.info(
+                "DiffSync model flag (%s) applied to object %s", model_flag, nautobot_obj,
+            )
+            diffsync_obj.model_flags = model_flag
+        return diffsync_obj
+
+    def _check_nautobot_version(self):
+        """Check the version of Nautobot defined in the configuration and load the proper models as needed.
+
+        The default models should work with the latest version of Nautobot
+        Version specific models should be used to manage older version.
+        """
+        try:
+            self.nautobot_version = Version(self.nautobot.version)
+        except InvalidVersion:
+            LOGGER.warning(
+                "Unable to identify the current version of Nautobot from Pynautobot, using the default version."
+            )
+            return
+
+    def load(self):
+        """Initialize pynautobot and load all data from nautobot in the local cache."""
+        inventory_settings = InventorySettings(**config.SETTINGS.inventory.settings)
+        self.nautobot = pynautobot.api(url=inventory_settings.address, token=inventory_settings.token)
+        if not inventory_settings.verify_ssl:
+            self.nautobot.http_session.verify = False
+        else:
+            self.nautobot.http_session.verify = True
+
+        self._check_nautobot_version()
+
+        sites = {}
+        device_names = []
+
+        results = self.nornir.run(task=query_device_info_from_nautobot)
+
+        for device_name, items in results.items():
+            if items[0].failed:
+                continue
+
+            result = items[0].result
+            nb_device = result["device"]
+            site_name = nb_device["site"].get("slug")
+
+            if site_name not in sites.keys():
+                site = self.site(name=site_name, remote_id=nb_device["site"].get("id"))
+                sites[site_name] = site
+                self.add(site)
+            else:
+                site = sites[site_name]
+
+            device = self.device(name=device_name, site_name=site_name, remote_id=nb_device["id"])
+
+            if nb_device["primary_ip"]:
+                device.primary_ip = nb_device["primary_ip"].get("address")
+
+            device = self.apply_model_flag(device, nb_device)
+            self.add(device)
+
+        # Load Prefix and Vlan per site
+        for site in self.get_all(self.site):
+            self.load_nautobot_prefix(site)
+            self.load_nautobot_vlan(site)
+
+        # Load interfaces and IP addresses for each devices
+        devices = self.get_all(self.device)
+        for device in devices:
+            site = sites[device.site_name]
+            device_names.append(device.name)
+            self.load_nautobot_device(site=site, device=device)
+
+        # Load Cabling
+        for site in self.get_all(self.site):
+            self.load_nautobot_cable(site=site, device_names=device_names)
+
+    def load_nautobot_device(self, site, device):
+        """Import all interfaces and IP address from Nautobot for a given device.
+
+        Args:
+            site (NautobotSite): Site the device is part of
+            device (DiffSyncModel): Device to import
+        """
+        self.load_nautobot_interface(site=site, device=device)
+        self.load_nautobot_ip_address(site=site, device=device)
+
+    def load_nautobot_prefix(self, site):
+        """Import all prefixes from Nautobot for a given site.
+
+        Args:
+            site (NautobotSite): Site to import prefix for
+        """
+        if not config.SETTINGS.main.import_prefixes:
+            return
+
+        prefixes = self.nautobot.ipam.prefixes.filter(site=site.name, status="active")
+
+        for nb_prefix in prefixes:
+
+            prefix = self.prefix(prefix=nb_prefix.prefix, site_name=site.name, remote_id=nb_prefix.id,)
+            prefix = self.apply_model_flag(prefix, nb_prefix)
+
+            if nb_prefix.vlan:
+                prefix.vlan = self.vlan.create_unique_id(vid=nb_prefix.vlan.vid, site_name=site.name)
+
+            self.add(prefix)
+            site.add_child(prefix)
+
+    def load_nautobot_vlan(self, site):
+        """Import all vlans from Nautobot for a given site.
+
+        Args:
+            site (NautobotSite): Site to import vlan for
+        """
+        if config.SETTINGS.main.import_vlans in [False, "no"]:
+            return
+
+        vlans = self.nautobot.ipam.vlans.filter(site=site.name)
+
+        for nb_vlan in vlans:
+            vlan = self.vlan.create_from_pynautobot(diffsync=self, obj=nb_vlan, site_name=site.name)
+            self.add(vlan)
+            site.add_child(vlan)
+
+    def convert_interface_from_nautobot(
+        self, device, intf, site=None
+    ):  # pylint: disable=too-many-branches,too-many-statements
+        """Convert PyNautobot interface object to NautobotInterface model.
+
+        Args:
+            site (NautobotSite): [description]
+            device (NautobotDevice): [description]
+            intf (pynautobot interface object): [description]
+        """
+        interface = self.interface(
+            name=intf.name,
+            device_name=device.name,
+            remote_id=intf.id,
+            description=intf.description or None,
+            mtu=intf.mtu,
+        )
+
+        import_vlans = False
+        if config.SETTINGS.main.import_vlans not in [False, "no"]:
+            import_vlans = True
+
+        # Define status if it's enabled in the config file
+        if config.SETTINGS.main.import_intf_status:
+            interface.active = intf.enabled
+
+        # Identify if the interface is physical or virtual and if it's part of a Lag
+        if intf.type and intf.type.value == "lag":
+            interface.is_lag = True
+            interface.is_virtual = False
+        elif intf.type and intf.type.value == "virtual":
+            interface.is_virtual = True
+            interface.is_lag = False
+        else:
+            interface.is_lag = False
+            interface.is_virtual = False
+
+        if intf.lag:
+            interface.is_lag_member = True
+            interface.is_lag = False
+            interface.is_virtual = False
+            parent_interface_uid = self.interface(name=intf.lag.name, device_name=device.name).get_unique_id()
+            interface.parent = parent_interface_uid
+
+        # identify Interface Mode
+        if intf.mode and intf.mode.value == "access":
+            interface.switchport_mode = "ACCESS"
+            interface.mode = interface.switchport_mode
+        elif intf.mode and intf.mode.value == "tagged":
+            interface.switchport_mode = "TRUNK"
+            interface.mode = interface.switchport_mode
+        elif not intf.mode and intf.tagged_vlans:
+            interface.switchport_mode = "NONE"
+            interface.mode = "L3_SUB_VLAN"
+        else:
+            interface.switchport_mode = "NONE"
+            interface.mode = "NONE"
+
+        # Identify Interface Speed based on the type
+        if intf.type and intf.type.value == 800:
+            interface.speed = 1000000000
+        elif intf.type and intf.type.value == 1100:
+            interface.speed = 1000000000
+        elif intf.type and intf.type.value == 1200:
+            interface.speed = 10000000000
+        elif intf.type and intf.type.value == 1350:
+            interface.speed = 25000000000
+        elif intf.type and intf.type.value == 1400:
+            interface.speed = 40000000000
+        elif intf.type and intf.type.value == 1600:
+            interface.speed = 100000000000
+
+        if site and intf.tagged_vlans and import_vlans:
+            for vid in [v.vid for v in intf.tagged_vlans]:
+                try:
+                    vlan = self.get(self.vlan, identifier=dict(vid=vid, site_name=site.name))
+                    interface.allowed_vlans.append(vlan.get_unique_id())
+                except ObjectNotFound:
+                    LOGGER.debug("%s | VLAN %s is not present for site %s", self.name, vid, site.name)
+
+        if site and intf.untagged_vlan and import_vlans:
+            try:
+                vlan = self.get(self.vlan, identifier=dict(vid=intf.untagged_vlan.vid, site_name=site.name))
+                interface.access_vlan = vlan.get_unique_id()
+            except ObjectNotFound:
+                LOGGER.debug("%s | VLAN %s is not present for site %s", self.name, intf.untagged_vlan.vid, site.name)
+
+        if intf.connected_endpoint_type:
+            interface.connected_endpoint_type = intf.connected_endpoint_type
+
+        new_intf, created = self.get_or_add(interface)
+        if created:
+            device.add_child(new_intf)
+
+        return new_intf
+
+    def load_nautobot_interface(self, site, device):
+        """Import all interfaces & Ips from Nautobot for a given device.
+
+        Args:
+            site (NautobotSite): DiffSync object representing a site
+            device (NautobotDevice): DiffSync object representing the device
+        """
+        intfs = self.nautobot.dcim.interfaces.filter(device=device.name)
+        for intf in intfs:
+            self.convert_interface_from_nautobot(site=site, device=device, intf=intf)
+
+        LOGGER.debug("%s | Found %s interfaces for %s", self.name, len(intfs), device.name)
+
+    def load_nautobot_ip_address(self, site, device):  # pylint: disable=unused-argument
+        """Import all IP addresses from Nautobot for a given device.
+
+        Args:
+            site (NautobotSite): DiffSync object representing a site
+            device (NautobotDevice): DiffSync object representing the device
+        """
+        if not config.SETTINGS.main.import_ips:
+            return
+
+        ips = self.nautobot.ipam.ip_addresses.filter(device=device.name)
+        for ipaddr in ips:
+            ip_address = self.ip_address.create_from_pynautobot(diffsync=self, obj=ipaddr, device_name=device.name)
+            ip_address, _ = self.get_or_add(ip_address)
+
+            interface = self.get(
+                self.interface, identifier=dict(device_name=device.name, name=ip_address.interface_name)
+            )
+            try:
+                interface.add_child(ip_address)
+            except ObjectAlreadyExists:
+                LOGGER.error(
+                    "%s | Duplicate IP found for %s (%s) ; IP already imported.", self.name, ip_address, device.name
+                )
+
+        LOGGER.debug("%s | Found %s ip addresses for %s", self.name, len(ips), device.name)
+
+    def load_nautobot_cable(self, site, device_names):
+        """Import all Cables from Nautobot for a given site.
+
+        If both devices at each end of the cables are not in the list of device_names, the cable will be ignored.
+
+        Args:
+            site (Site): Site object to import cables for
+            device_names (list): List of device names that are part of the inventory
+        """
+        cables = self.nautobot.dcim.cables.filter(site=site.name)
+
+        nbr_cables = 0
+        for nb_cable in cables:
+            if nb_cable.termination_a_type != "dcim.interface" or nb_cable.termination_b_type != "dcim.interface":
+                continue
+
+            if (nb_cable.termination_a.device.name not in device_names) and (
+                nb_cable.termination_b.device.name not in device_names
+            ):
+                LOGGER.debug(
+                    "%s | Skipping cable %s because neither devices (%s, %s) is in the list of devices",
+                    self.name,
+                    nb_cable.id,
+                    nb_cable.termination_a.device.name,
+                    nb_cable.termination_b.device.name,
+                )
+                continue
+
+            # Disabling this check for now until we are able to allow user to control how cabling should be imported
+            # if nb_cable.termination_a.device.name not in device_names:
+            #     LOGGER.debug(
+            #         "%s | Skipping cable %s because %s is not in the list of devices",
+            #         self.name,
+            #         nb_cable.id,
+            #         nb_cable.termination_a.device.name,
+            #     )
+            #     continue
+
+            # if nb_cable.termination_b.device.name not in device_names:
+            #     LOGGER.debug(
+            #         "%s | Skipping cable %s because %s is not in the list of devices",
+            #         self.name,
+            #         nb_cable.id,
+            #         nb_cable.termination_b.device.name,
+            #     )
+            #     continue
+
+            cable = self.cable(
+                device_a_name=nb_cable.termination_a.device.name,
+                interface_a_name=nb_cable.termination_a.name,
+                device_z_name=nb_cable.termination_b.device.name,
+                interface_z_name=nb_cable.termination_b.name,
+                remote_id=nb_cable.id,
+                status="connected",
+            )
+
+            try:
+                self.add(cable)
+            except ObjectAlreadyExists:
+                pass
+
+            nbr_cables += 1
+
+        LOGGER.debug("%s | Found %s cables in nautobot for %s", self.name, nbr_cables, site.name)
+
+    def get_intf_from_nautobot(self, device_name, intf_name):
+        """Get an interface from Nautobot based on the name of the device and the name of the interface.
+
+        Exactly one return must be returned from Nautobot, the function will return False if more than 1 result are returned.
+
+        Args:
+            device_name (str): name of the device in nautobot
+            intf_name (str): name of the interface in Nautobot
+
+        Returns:
+            NautobotInterface, bool: Interface in DiffSync format
+        """
+        intfs = self.nautobot.dcim.interfaces.filter(name=intf_name, device=device_name)
+
+        if len(intfs) == 0:
+            # LOGGER.debug("Unable to find the interface in Nautobot for %s %s, nothing returned", device_name, intf_name)
+            return False
+
+        if len(intfs) > 1:
+            LOGGER.warning(
+                "Unable to find the proper interface in Nautobot for %s %s, more than 1 element returned",
+                device_name,
+                intf_name,
+            )
+            return False
+
+        intf = self.interface(name=intf_name, device_name=device_name, remote_id=intfs[0].id)
+        intf = self.apply_model_flag(intf, intfs[0])
+
+        if intfs[0].connected_endpoint_type:
+            intf.connected_endpoint_type = intfs[0].connected_endpoint_type
+
+        self.add(intf)
+
+        return intf
diff --git a/network_importer/adapters/nautobot_api/exceptions.py b/network_importer/adapters/nautobot_api/exceptions.py
new file mode 100644
index 00000000..bb6c6f8c
--- /dev/null
+++ b/network_importer/adapters/nautobot_api/exceptions.py
@@ -0,0 +1,5 @@
+"""Custom Exceptions for the NautobotAPIAdapter."""
+
+
+class NautobotObjectNotValid(Exception):
+    """Exception to indicate that a given Nautobot object is not valid."""
diff --git a/network_importer/adapters/nautobot_api/inventory.py b/network_importer/adapters/nautobot_api/inventory.py
new file mode 100644
index 00000000..4533b7f8
--- /dev/null
+++ b/network_importer/adapters/nautobot_api/inventory.py
@@ -0,0 +1,145 @@
+"""Norning Inventory for nautobot."""
+# Disable too-many-arguments and too-many-locals pylint tests for this file. These are both necessary
+# pylint: disable=R0913,R0914,E1101,W0613
+
+import sys
+from typing import Any, List
+import pynautobot
+from pydantic import ValidationError
+
+from nornir.core.inventory import Defaults, Groups, Hosts, Inventory, ParentGroups, ConnectionOptions
+from nornir.core.plugins.inventory import InventoryPluginRegister
+from network_importer.inventory import NetworkImporterInventory, NetworkImporterHost
+from network_importer.utils import build_filter_params
+from network_importer.adapters.nautobot_api.settings import InventorySettings
+
+
+class NautobotAPIInventory(NetworkImporterInventory):
+    """Nautobot API Inventory Class."""
+
+    # pylint: disable=dangerous-default-value, too-many-branches, too-many-statements
+    def __init__(self, *args, **kwargs: Any,) -> None:
+        """Nornir Inventory Plugin for Nautobot API."""
+        super().__init__(
+            *args, **kwargs,
+        )
+
+        try:
+            self.settings = InventorySettings(**self.settings)
+        except ValidationError as exc:
+            print(f"Inventory Settings are not valid, found {len(exc.errors())} error(s)")
+            for error in exc.errors():
+                print(f"  inventory/{'/'.join(error['loc'])} | {error['msg']} ({error['type']})")
+            sys.exit(1)
+
+        # Build Filter based on inventory_settings filter and on limit
+        self.filter_parameters = {}
+        if self.settings.filter is not None:
+            build_filter_params(self.settings.filter.split((",")), self.filter_parameters)
+
+        if self.limit:
+            if "=" not in self.limit:
+                self.filter_parameters["name"] = self.limit
+            else:
+                build_filter_params(self.limit.split((",")), self.filter_parameters)
+
+        if "exclude" not in self.filter_parameters.keys():
+            self.filter_parameters["exclude"] = "config_context"
+
+        # Instantiate nautobot session using pynautobot
+        self.session = pynautobot.api(url=self.settings.address, token=self.settings.token)
+        if not self.settings.verify_ssl:
+            self.session.http_session.verify = False
+
+    def load(self):
+        """Load inventory by fetching devices from nautobot."""
+        if self.filter_parameters:
+            devices: List[pynautobot.modules.dcim.Devices] = self.session.dcim.devices.filter(**self.filter_parameters)
+        else:
+            devices: List[pynautobot.modules.dcim.Devices] = self.session.dcim.devices.all()
+
+        # fetch all platforms from nautobot and build mapping:   platform:  napalm_driver
+        platforms = self.session.dcim.platforms.all()
+        platforms_mapping = {platform.slug: platform.napalm_driver for platform in platforms if platform.napalm_driver}
+
+        hosts = Hosts()
+        groups = Groups()
+        defaults = Defaults()
+
+        for dev in devices:
+            # nautobot allows devices to be unnamed, but the Nornir model does not allow this
+            # If a device is unnamed we will set the name to the id of the device in nautobot
+            dev_name = dev.name or dev.id
+            host = NetworkImporterHost(name=dev_name, connection_options=ConnectionOptions())
+
+            # Only add virtual chassis master as inventory element
+            if dev.virtual_chassis and dev.virtual_chassis.master:
+                if dev.id != dev.virtual_chassis.master.id:
+                    continue
+                host.data["virtual_chassis"] = True
+
+            else:
+                host.data["virtual_chassis"] = False
+
+            # If supported_platforms is provided
+            # skip all devices that do not match the list of supported platforms
+            # TODO need to see if we can filter when doing the query directly
+            if self.supported_platforms:
+                if not dev.platform:
+                    continue
+
+                if dev.platform.slug not in self.supported_platforms:
+                    continue
+
+            # Add value for IP address
+            if self.settings.use_primary_ip and dev.primary_ip:
+                host.hostname = dev.primary_ip.address.split("/")[0]
+            elif self.settings.use_primary_ip and not dev.primary_ip:
+                host.is_reachable = False
+                host.not_reachable_reason = "primary ip not defined in nautobot"
+            elif not self.settings.use_primary_ip and self.settings.fqdn:
+                host.hostname = f"{dev.name}.{self.settings.fqdn}"
+            elif not self.settings.use_primary_ip:
+                host.hostname = dev.name
+            else:
+                host.hostname = dev_name
+
+            host.site_name = dev.site.slug
+
+            host.data["serial"] = dev.serial
+            host.data["vendor"] = dev.device_type.manufacturer.slug
+            host.data["asset_tag"] = dev.asset_tag
+            host.data["custom_fields"] = dev.custom_fields
+            host.data["site_id"] = dev.site.id
+            host.data["device_id"] = dev.id
+            host.data["role"] = dev.device_role.slug
+            host.data["model"] = dev.device_type.slug
+
+            # Attempt to add 'platform' based of value in 'slug'
+            if dev.platform and dev.platform.slug in platforms_mapping:
+                host.connection_options = {"napalm": ConnectionOptions(platform=platforms_mapping[dev.platform.slug])}
+
+            if dev.platform:
+                host.platform = dev.platform.slug
+            else:
+                host.platform = None
+
+            host.groups = ParentGroups([self.global_group])
+
+            if dev.site.slug not in groups.keys():
+                groups[dev.site.slug] = {}
+
+            if dev.device_role.slug not in groups.keys():
+                groups[dev.device_role.slug] = {}
+
+            if host.hostname and host.platform:
+                host.is_reachable = True
+
+            # Assign temporary dict to outer dict
+
+            hosts[dev_name] = host
+
+        return Inventory(hosts=hosts, groups=groups, defaults=defaults)
+
+
+InventoryPluginRegister.register("NautobotAPIInventory", NautobotAPIInventory)
diff --git a/network_importer/adapters/nautobot_api/models.py b/network_importer/adapters/nautobot_api/models.py
new file mode 100644
index 00000000..2e86a6da
--- /dev/null
+++ b/network_importer/adapters/nautobot_api/models.py
@@ -0,0 +1,721 @@
+"""Extension of the base Models for the NautobotAPIAdapter."""
+from typing import Optional
+import logging
+
+import pynautobot
+from diffsync.exceptions import ObjectNotFound
+from diffsync import DiffSync, DiffSyncModel  # pylint: disable=unused-import
+
+import network_importer.config as config  # pylint: disable=import-error
+from network_importer.adapters.nautobot_api.exceptions import NautobotObjectNotValid
+from network_importer.models import (  # pylint: disable=import-error
+    Site,
+    Device,
+    Interface,
+    IPAddress,
+    Cable,
+    Prefix,
+    Vlan,
+)
+
+LOGGER = logging.getLogger("network-importer")
+
+
+class NautobotSite(Site):
+    """Extension of the Site model."""
+
+    remote_id: Optional[str]
+
+
+class NautobotDevice(Device):
+    """Extension of the Device model."""
+
+    remote_id: Optional[str]
+    primary_ip: Optional[str]
+
+    device_tag_id: Optional[str]
+
+    def get_device_tag_id(self):
+        """Get the Nautobot id of the tag for this device.
+
+        If the ID is already present locally return it
+        If not try to retrieve it from Nautobot or create it in Nautobot if needed
+
+        Returns:
+            device_tag_id (str)
+        """
+        if self.device_tag_id:
+            return self.device_tag_id
+
+        tag = self.diffsync.nautobot.extras.tags.get(name=f"device={self.name}")
+
+        if not tag:
+            tag = self.diffsync.nautobot.extras.tags.create(name=f"device={self.name}", slug=f"device__{self.name}")
+
+        self.device_tag_id = tag.id
+        return self.device_tag_id
+
+
+class NautobotInterface(Interface):
+    """Extension of the Interface model."""
+
+    remote_id: Optional[str]
+    connected_endpoint_type: Optional[str]
+
+    def translate_attrs_for_nautobot(self, attrs):  # pylint: disable=too-many-branches
+        """Translate interface attributes into Nautobot format.
+
+        Args:
+            params (dict): Dictionnary of attributes of the object to translate
+
+        Returns:
+            dict: Nautobot parameters
+        """
+
+        def convert_vlan_to_nid(vlan_uid):
+            if not vlan_uid:
+                return None
+            try:
+                vlan = self.diffsync.get(self.diffsync.vlan, identifier=vlan_uid)
+            except ObjectNotFound:
+                return None
+
+            return vlan.remote_id
+
+        def convert_vlan_list_to_nids(vlan_uids):
+            resp = []
+            for uid in vlan_uids:
+                nid = convert_vlan_to_nid(uid)
+                if nid:
+                    resp.append(nid)
+            return resp
+
+        # Reconstruct all attrs of the object in its expected state
+        complete_attrs = self.get_attrs()
+        complete_attrs.update(attrs)
+        attrs = complete_attrs
+
+        nb_params = {}
+
+        # Identify the id of the device this interface is attached to
+        device = self.diffsync.get(self.diffsync.device, identifier=self.device_name)
+
+        if not device.remote_id:
+            raise NautobotObjectNotValid(f"device {self.device_name}, is missing a remote_id")
+
+        nb_params["device"] = device.remote_id
+        nb_params["name"] = self.name
+
+        if "is_lag" in attrs and attrs["is_lag"]:
+            nb_params["type"] = "lag"
+        elif "is_virtual" in attrs and attrs["is_virtual"]:
+            nb_params["type"] = "virtual"
+        else:
+            nb_params["type"] = "other"
+
+        if "mtu" in attrs:
+            nb_params["mtu"] = attrs["mtu"]
+
+        if "description" in attrs:
+            nb_params["description"] = attrs["description"] or ""
+
+        if "switchport_mode" in attrs and attrs["switchport_mode"] == "ACCESS":
+            nb_params["mode"] = "access"
+        elif "switchport_mode" in attrs and attrs["switchport_mode"] == "TRUNK":
+            nb_params["mode"] = "tagged"
+
+        # if is None:
+        #     intf_properties["enabled"] = intf.active
+
+        if config.SETTINGS.main.import_vlans not in [False, "no"]:
+            if "mode" in attrs and attrs["mode"] in ["TRUNK", "ACCESS"] and "access_vlan" in attrs:
+                nb_params["untagged_vlan"] = convert_vlan_to_nid(attrs["access_vlan"])
+
+            if (
+                "mode" in attrs
+                and attrs["mode"] in ["TRUNK", "L3_SUB_VLAN"]
+                and "allowed_vlans" in attrs
+                and attrs["allowed_vlans"]
+            ):
+                nb_params["tagged_vlans"] = convert_vlan_list_to_nids(attrs["allowed_vlans"])
+            elif (
+                "mode" in attrs
+                and attrs["mode"] in ["TRUNK", "L3_SUB_VLAN"]
+                and ("allowed_vlans" not in attrs or not attrs["allowed_vlans"])
+            ):
+                nb_params["tagged_vlans"] = []
+
+        if "is_lag_member" in attrs and attrs["is_lag_member"] and "parent" in attrs:
+            try:
+                parent_interface = self.diffsync.get(self.diffsync.interface, identifier=attrs["parent"])
+                if parent_interface and parent_interface.remote_id:
+                    nb_params["lag"] = parent_interface.remote_id
+            except ObjectNotFound:
+                LOGGER.warning("No Parent interface found for lag member %s %s", self.device_name, self.name)
+                nb_params["lag"] = None
+
+        elif "is_lag_member" in attrs and not attrs["is_lag_member"]:
+            nb_params["lag"] = None
+
+        return nb_params
+
+    @classmethod
+    def create(cls, diffsync: "DiffSync", ids: dict, attrs: dict) -> Optional["DiffSyncModel"]:
+        """Create an interface object in Nautobot.
+
+        Args:
+            diffsync: The master data store for other DiffSyncModel instances that we might need to reference
+            ids: Dictionary of unique-identifiers needed to create the new object
+            attrs: Dictionary of additional attributes to set on the new object
+
+        Returns:
+            NautobotInterface: DiffSync object newly created
+        """
+        item = super().create(ids=ids, diffsync=diffsync, attrs=attrs)
+
+        try:
+            nb_params = item.translate_attrs_for_nautobot(attrs)
+            intf = diffsync.nautobot.dcim.interfaces.create(**nb_params)
+            LOGGER.info("Created interface %s (%s) in Nautobot", intf.name, intf.id)
+        except pynautobot.core.query.RequestError as exc:
+            LOGGER.warning(
+                "Unable to create interface %s on %s in %s (%s)",
+                ids["name"],
+                ids["device_name"],
+                diffsync.name,
+                exc.error,
+            )
+            return item
+        except Exception as exc:  # pylint: disable=broad-except
+            LOGGER.warning(
+                "Unable to create interface %s on %s in %s (%s)",
+                ids["name"],
+                ids["device_name"],
+                diffsync.name,
+                str(exc),
+            )
+            return item
+
+        item.remote_id = intf.id
+        return item
+
+    def update(self, attrs: dict) -> Optional["DiffSyncModel"]:
+        """Update an interface object in Nautobot.
+
+        Args:
+            attrs: Dictionary of attributes to update on the object
+
+        Returns:
+            DiffSyncModel: this instance, if all data was successfully updated.
+            None: if data updates failed in such a way that child objects of this model should not be modified.
+
+        Raises:
+            ObjectNotUpdated: if an error occurred.
+        """
+        current_attrs = self.get_attrs()
+
+        if attrs == current_attrs:
+            return self
+
+        current_attrs.update(attrs)
+        nb_params = self.translate_attrs_for_nautobot(current_attrs)
+        LOGGER.debug("Update interface : %s", nb_params)
+        try:
+            intf = self.diffsync.nautobot.dcim.interfaces.get(self.remote_id)
+            intf.update(data=nb_params)
+            LOGGER.info("Updated Interface %s %s (%s) in Nautobot", self.device_name, self.name, self.remote_id)
+        except pynautobot.core.query.RequestError as exc:
+            LOGGER.warning(
+                "Unable to update interface %s on %s in %s (%s)",
+                self.name,
+                self.device_name,
+                self.diffsync.name,
+                exc.error,
+            )
+            return None
+
+        return super().update(attrs)
+
+    def delete(self) -> Optional["DiffSyncModel"]:
+        """Delete an interface object in Nautobot.
+
+        Returns:
+            NautobotInterface: DiffSync object
+        """
+        # Check if the interface has some Ips, check if it is the management interface
+        if self.ips:
+            try:
+                dev = self.diffsync.get(self.diffsync.device, identifier=self.device_name)
+                if dev.primary_ip and dev.primary_ip in self.ips:
+                    LOGGER.warning(
+                        "Unable to delete interface %s on %s, because it's currently the management interface",
+                        self.name,
+                        dev.name,
+                    )
+                    return self
+            except ObjectNotFound:
+                LOGGER.debug(
+                    "Unable to delete interface %s on %s, because device is not present. See issue #188",
+                    self.name,
+                    self.device_name,
+                )
+                return None
+        try:
+            intf = self.diffsync.nautobot.dcim.interfaces.get(self.remote_id)
+            intf.delete()
+        except pynautobot.core.query.RequestError as exc:
+            LOGGER.warning(
+                "Unable to delete Interface %s on %s in %s (%s)",
+                self.name,
+                self.device_name,
+                self.diffsync.name,
+                exc.error,
+            )
+
+        super().delete()
+        return self
+
+
+class NautobotIPAddress(IPAddress):
+    """Extension of the IPAddress model."""
+
+    remote_id: Optional[str]
+
+    def translate_attrs_for_nautobot(self, attrs=None):  # pylint: disable=unused-argument
+        """Translate IP address attributes into Nautobot format.
+
+        Args:
+            attrs (dict): Dictionnary of attributes of the object to translate
+
+        Returns:
+            dict: Nautobot parameters
+        """
+        nb_params = {"address": self.address}
+
+        try:
+            interface = self.diffsync.get(
+                self.diffsync.interface, identifier=dict(device_name=self.device_name, name=self.interface_name),
+            )
+            nb_params["assigned_object_type"] = "dcim.interface"
+            nb_params["assigned_object_id"] = interface.remote_id
+        except ObjectNotFound:
+            pass
+
+        return nb_params
+
+    @classmethod
+    def create_from_pynautobot(cls, diffsync: "DiffSync", obj, device_name):  # pylint: disable=unused-argument
+        """Create a new NautobotIPAddress object from a pynautobot ip_address object.
+
+        Args:
+            diffsync (DiffSync): Nautobot API Adapter
+            obj (pynautobot.models.ipam.IpAddresses): IPAddress object returned by pynautobot
+            device_name (str): name of the device associated with this ip address
+        Returns:
+            NautobotIPAddress: DiffSync object
+        """
+        item = cls(
+            address=obj.address, device_name=device_name, interface_name=obj.assigned_object.name, remote_id=obj.id
+        )
+
+        item = diffsync.apply_model_flag(item, obj)
+        return item
+
+    @classmethod
+    def create(cls, diffsync: "DiffSync", ids: dict, attrs: dict) -> Optional["DiffSyncModel"]:
+        """Create an IP address in Nautobot, if the name of a valid interface is provided the interface will be assigned to the interface.
+
+        Returns:
+            NautobotIPAddress: DiffSync object
+        """
+        try:
+            item = super().create(ids=ids, diffsync=diffsync, attrs=attrs)
+            nb_params = item.translate_attrs_for_nautobot(attrs)
+            # Add status because it's a mandatory field.
+            nb_params["status"] = "active"
+            ip_address = diffsync.nautobot.ipam.ip_addresses.create(**nb_params)
+        except pynautobot.core.query.RequestError as exc:
+            LOGGER.warning("Unable to create the ip address %s in %s (%s)", ids["address"], diffsync.name, exc.error)
+            return None
+
+        LOGGER.info("Created IP %s (%s) in Nautobot", ip_address.address, ip_address.id)
+        item.remote_id = ip_address.id
+
+        return item
+
+    def delete(self) -> Optional["DiffSyncModel"]:
+        """Delete an IP address in Nautobot.
+
+        Returns:
+            NautobotIPAddress: DiffSync object
+        """
+        if self.device_name:
+            try:
+                dev = self.diffsync.get(self.diffsync.device, identifier=self.device_name)
+                if dev.primary_ip == self.address:
+                    LOGGER.warning(
+                        "Unable to delete IP Address %s on %s, because it's currently the management IP address",
+                        self.address,
+                        self.device_name,
+                    )
+                    return None
+            except ObjectNotFound:
+                LOGGER.debug(
+                    "Unable to delete IP Address %s on %s, because device is not present. See issue #188",
+                    self.address,
+                    self.device_name,
+                )
+                return None
+        try:
+            ipaddr = self.diffsync.nautobot.ipam.ip_addresses.get(self.remote_id)
+            ipaddr.delete()
+        except pynautobot.core.query.RequestError as exc:
+            LOGGER.warning(
+                "Unable to delete IP Address %s on %s in %s (%s)",
+                self.address,
+                self.device_name,
+                self.diffsync.name,
+                exc.error,
+            )
+            return None
+
+        super().delete()
+        return self
+
+
+class NautobotPrefix(Prefix):
+    """Extension of the Prefix model."""
+
+    remote_id: Optional[str]
+
+    def translate_attrs_for_nautobot(self, attrs):
+        """Translate prefix attributes into Nautobot format.
+
+        Args:
+            attrs (dict): Dictionnary of attributes of the object to translate
+
+        Returns:
+            dict: Nautobot parameters
+        """
+        nb_params = {"prefix": self.prefix, "status": "active"}
+
+        site = self.diffsync.get(self.diffsync.site, identifier=self.site_name)
+        nb_params["site"] = site.remote_id
+
+        if "vlan" in attrs and attrs["vlan"]:
+            try:
+                vlan = self.diffsync.get(self.diffsync.vlan, identifier=attrs["vlan"])
+                if vlan.remote_id:
+                    nb_params["vlan"] = vlan.remote_id
+            except ObjectNotFound:
+                pass
+
+        return nb_params
+
+    @classmethod
+    def create(cls, diffsync: "DiffSync", ids: dict, attrs: dict) -> Optional["DiffSyncModel"]:
+        """Create a Prefix in Nautobot.
+
+        Returns:
+            NautobotPrefix: DiffSync object
+        """
+        item = super().create(ids=ids, diffsync=diffsync, attrs=attrs)
+        nb_params = item.translate_attrs_for_nautobot(attrs)
+
+        try:
+            prefix = diffsync.nautobot.ipam.prefixes.create(**nb_params)
+            LOGGER.info("Created Prefix %s (%s) in Nautobot", prefix.prefix, prefix.id)
+        except pynautobot.core.query.RequestError as exc:
+            LOGGER.warning("Unable to create Prefix %s in %s (%s)", ids["prefix"], diffsync.name, exc.error)
+            return None
+
+        item.remote_id = prefix.id
+
+        return item
+
+    def update(self, attrs: dict) -> Optional["DiffSyncModel"]:
+        """Update a Prefix object in Nautobot.
+
+        Args:
+            attrs: Dictionary of attributes to update on the object
+
+        Returns:
+            DiffSyncModel: this instance, if all data was successfully updated.
+            None: if data updates failed in such a way that child objects of this model should not be modified.
+
+        Raises:
+            ObjectNotUpdated: if an error occurred.
+        """
+        current_attrs = self.get_attrs()
+
+        if attrs == current_attrs:
+            return self
+
+        nb_params = self.translate_attrs_for_nautobot(attrs)
+
+        try:
+            prefix = self.diffsync.nautobot.ipam.prefixes.get(self.remote_id)
+            prefix.update(data=nb_params)
+            LOGGER.info("Updated Prefix %s (%s) in Nautobot", self.prefix, self.remote_id)
+        except pynautobot.core.query.RequestError as exc:
+            LOGGER.warning(
+                "Unable to update perfix %s in %s (%s)", self.prefix, self.diffsync.name, exc.error,
+            )
+            return None
+
+        return super().update(attrs)
+
+
+class NautobotVlan(Vlan):
+    """Extension of the Vlan model."""
+
+    remote_id: Optional[str]
+    tag_prefix: str = "device="
+
+    def translate_attrs_for_nautobot(self, attrs):
+        """Translate vlan attributes into Nautobot format.
+
+        Args:
+            attrs (dict): Dictionnary of attributes of the object to translate
+
+        Returns:
+            dict: Nautobot parameters
+        """
+        nb_params = {"vid": self.vid}
+
+        if "name" in attrs and attrs["name"]:
+            nb_params["name"] = attrs["name"]
+        elif not self.name:
+            nb_params["name"] = f"vlan-{self.vid}"
+
+        site = self.diffsync.get(self.diffsync.site, identifier=self.site_name)
+        nb_params["site"] = site.remote_id
+
+        # Add Status
+        nb_params["status"] = "Active"
+
+        if "associated_devices" in attrs:
+            nb_params["tags"] = []
+            for device_name in attrs["associated_devices"]:
+                try:
+                    device = self.diffsync.get(self.diffsync.device, identifier=device_name)
+                except ObjectNotFound:
+                    LOGGER.error(
+                        "Found an associated device on Vlan %s that doesn't exist (%s)",
+                        self.get_unique_id(),
+                        device_name,
+                    )
+                    continue
+
+                tag_id = device.get_device_tag_id()
+                nb_params["tags"].append(tag_id)
+
+        return nb_params
+
+    @classmethod
+    def create_from_pynautobot(cls, diffsync: "DiffSync", obj, site_name):
+        """Create a new NautobotVlan object from a pynautobot vlan object.
+
+        Args:
+            diffsync (DiffSync): Nautobot API Adapter
+            obj (pynautobot.models.ipam.Vlans): Vlan object returned by Pynautobot
+            site_name (str): name of the site associated with this vlan
+
+        Returns:
+            NautobotVlan: DiffSync object
+        """
+        item = cls(vid=obj.vid, site_name=site_name, name=obj.name, remote_id=obj.id)
+
+        # Check the existing tags to learn which device is already associated with this vlan
+        # Exclude all devices that are not part of the inventory
+        for tag in obj.tags:
+            if item.tag_prefix not in tag["name"]:
+                continue
+
+            device_name = tag["name"].split(item.tag_prefix)[1]
+            try:
+                device = diffsync.get(diffsync.device, identifier=device_name)
+            except ObjectNotFound:
+                device = None
+            if device:
+                item.add_device(device_name)
+                device.device_tag_id = tag["id"]
+
+        item = diffsync.apply_model_flag(item, obj)
+
+        return item
+
+    @classmethod
+    def create(cls, diffsync: "DiffSync", ids: dict, attrs: dict) -> Optional["DiffSyncModel"]:
+        """Create new Vlan in Nautobot.
+
+        Returns:
+            NautobotVlan: DiffSync object
+        """
+        try:
+            item = super().create(ids=ids, diffsync=diffsync, attrs=attrs)
+            nb_params = item.translate_attrs_for_nautobot(attrs)
+            vlan = diffsync.nautobot.ipam.vlans.create(**nb_params)
+            item.remote_id = vlan.id
+            LOGGER.info("Created Vlan %s in %s (%s)", item.get_unique_id(), diffsync.name, vlan.id)
+        except pynautobot.core.query.RequestError as exc:
+            LOGGER.warning("Unable to create Vlan %s in %s (%s)", ids, diffsync.name, exc.error)
+            return None
+
+        return item
+
+    def update_clean_tags(self, nb_params, obj):
+        """Update list of vlan tags with additinal tags that already exists on the object in nautobot.
+
+        Args:
+            nb_params (dict): dict of parameters in nautobot format
+            obj (pynautobot): Vlan object from pynautobot
+        """
+        # Before updating the remote vlan we need to check the existing list of tags
+        # to ensure that we won't delete an existing tags
+        if "tags" in nb_params and nb_params["tags"] and obj.tags:
+            for tag in obj.tags:
+                if self.tag_prefix not in tag["name"]:
+                    nb_params["tags"].append(tag["id"])
+                else:
+                    dev_name = tag["name"].split(self.tag_prefix)[1]
+                    try:
+                        res = self.diffsync.get(self.diffsync.device, identifier=dev_name)
+                    except ObjectNotFound:
+                        res = None
+                    if not res:
+                        nb_params["tags"].append(tag["id"])
+
+        return nb_params
+
+    def update(self, attrs: dict) -> Optional["DiffSyncModel"]:
+        """Update new Vlan in Nautobot.
+
+        Returns:
+            NautobotVlan: DiffSync object
+        """
+        nb_params = self.translate_attrs_for_nautobot(attrs)
+
+        try:
+            vlan = self.diffsync.nautobot.ipam.vlans.get(self.remote_id)
+            clean_params = self.update_clean_tags(nb_params=nb_params, obj=vlan)
+            vlan.update(data=clean_params)
+            LOGGER.info("Updated Vlan %s (%s) in Nautobot", self.get_unique_id(), self.remote_id)
+        except pynautobot.core.query.RequestError as exc:
+            LOGGER.warning("Unable to update Vlan %s in %s (%s)", self.get_unique_id(), self.diffsync.name, exc.error)
+            return None
+
+        return super().update(attrs)
+
+
+class NautobotCable(Cable):
+    """Extension of the Cable model."""
+
+    remote_id: Optional[str]
+    termination_a_id: Optional[str]
+    termination_z_id: Optional[str]
+
+    @classmethod
+    def create(cls, diffsync: "DiffSync", ids: dict, attrs: dict) -> Optional["DiffSyncModel"]:
+        """Create a Cable in Nautobot.
+
+        Returns:
+            NautobotCable: DiffSync object
+        """
+        item = super().create(ids=ids, diffsync=diffsync, attrs=attrs)
+
+        try:
+            interface_a = diffsync.get(
+                diffsync.interface, identifier=dict(device_name=ids["device_a_name"], name=ids["interface_a_name"])
+            )
+        except ObjectNotFound:
+            interface_a = diffsync.get_intf_from_nautobot(
+                device_name=ids["device_a_name"], intf_name=ids["interface_a_name"]
+            )
+            if not interface_a:
+                LOGGER.info(
+                    "Unable to create Cable %s in %s, unable to find the interface %s %s",
+                    item.get_unique_id(),
+                    diffsync.name,
+                    ids["device_a_name"],
+                    ids["interface_a_name"],
+                )
+                return item
+
+        try:
+            interface_z = diffsync.get(
+                diffsync.interface, identifier=dict(device_name=ids["device_z_name"], name=ids["interface_z_name"])
+            )
+        except ObjectNotFound:
+            interface_z = diffsync.get_intf_from_nautobot(
+                device_name=ids["device_z_name"], intf_name=ids["interface_z_name"]
+            )
+            if not interface_z:
+                LOGGER.info(
+                    "Unable to create Cable %s in %s, unable to find the interface %s %s",
+                    item.get_unique_id(),
+                    diffsync.name,
+                    ids["device_z_name"],
+                    ids["interface_z_name"],
+                )
+                return item
+
+        if interface_a.connected_endpoint_type:
+            LOGGER.info(
+                "Unable to create Cable in %s, port %s %s is already connected",
+                diffsync.name,
+                ids["device_a_name"],
+                ids["interface_a_name"],
+            )
+            return item
+
+        if interface_z.connected_endpoint_type:
+            LOGGER.info(
+                "Unable to create Cable in %s, port %s %s is already connected",
+                diffsync.name,
+                ids["device_z_name"],
+                ids["interface_z_name"],
+            )
+            return item
+
+        try:
+            cable = diffsync.nautobot.dcim.cables.create(
+                termination_a_type="dcim.interface",
+                termination_b_type="dcim.interface",
+                termination_a_id=interface_a.remote_id,
+                termination_b_id=interface_z.remote_id,
+                status="connected",
+            )
+        except pynautobot.core.query.RequestError as exc:
+            LOGGER.warning("Unable to create Cable %s in %s (%s)", ids, diffsync.name, exc.error)
+            return item
+
+        interface_a.connected_endpoint_type = "dcim.interface"
+        interface_z.connected_endpoint_type = "dcim.interface"
+
+        LOGGER.info("Created Cable %s (%s) in Nautobot", item.get_unique_id(), cable.id)
+        item.remote_id = cable.id
+
+        return item
+
+    def delete(self):
+        """Do not Delete the Cable in Nautobot, just print a warning message.
+
+        Returns:
+            NautobotCable: DiffSync object
+        """
+        LOGGER.warning(
+            "Cable %s is present in %s but not in the Network, please delete it manually if it shouldn't be in %s",
+            self.get_unique_id(),
+            self.diffsync.name,
+            self.diffsync.name,
+        )
+
+        # try:
+        #     cable = self.diffsync.nautobot.dcim.cables.get(self.remote_id)
+        #     cable.delete()
+        # except pynautobot.core.query.RequestError as exc:
+        #
+
+        super().delete()
+        return self
diff --git a/network_importer/adapters/nautobot_api/settings.py b/network_importer/adapters/nautobot_api/settings.py
new file mode 100644
index 00000000..1734ac3c
--- /dev/null
+++ b/network_importer/adapters/nautobot_api/settings.py
@@ -0,0 +1,33 @@
+"""Settings definition for the NetboxAPIAdapter."""
+from typing import List, Optional, Union
+from pydantic import BaseSettings
+
+from diffsync import DiffSyncModelFlags
+
+
+class AdapterSettings(BaseSettings):
+    """Config settings for the netbox_api adapter. Not used currently."""
+
+    model_flag_tags: List[str] = list()  # List of tags that defines what objects to assign the model_flag to.
+    model_flag: Optional[DiffSyncModelFlags]  # The model flag that will be applied to objects based on tag.
+
+
+class InventorySettings(BaseSettings):
+    """Config settings for the NautobotAPI inventory."""
+
+    address: Optional[str] = "http://localhost"
+    token: Optional[str] = None
+    verify_ssl: Union[bool, str] = True
+
+    use_primary_ip: Optional[bool] = True
+    fqdn: Optional[str] = None
+    filter: Optional[str] = None
+
+    class Config:
+        """Additional parameters to automatically map environment variable to some settings."""
+
+        fields = {
+            "address": {"env": "NAUTOBOT_ADDRESS"},
+            "token": {"env": "NAUTOBOT_TOKEN"},
+            "verify_ssl": {"env": "NAUTOBOT_VERIFY_SSL"},
+        }
diff --git a/network_importer/adapters/nautobot_api/tasks.py b/network_importer/adapters/nautobot_api/tasks.py
new file mode 100644
index 00000000..cf4fbd62
--- /dev/null
+++ b/network_importer/adapters/nautobot_api/tasks.py
@@ -0,0 +1,61 @@
+"""Collection of nornir tasks for the NetboxAPIAdapter."""
+import logging
+
+import pynautobot
+from nornir.core.task import Result, Task
+
+import network_importer.config as config  # pylint: disable=import-error
+from network_importer.adapters.nautobot_api.settings import InventorySettings
+
+LOGGER = logging.getLogger("network-importer")
+
+
+def query_device_info_from_nautobot(task: Task) -> Result:
+    """Nornir Task to query the device information from Nautobot.
+
+    Currently this task will pull both the device information but th goal is to pull additional information
+    and return everything in a single dict
+    TODO add logic to pull interfaces as well
+    TODO add logic to pull ip addresses as well
+
+    Args:
+        task (Task): Nornir Task with a valid network device
+
+    Returns:
+        Result: Nornir Result object with the result in a dict format
+    """
+    inventory_settings = InventorySettings(**config.SETTINGS.inventory.settings)
+    nautobot = pynautobot.api(url=inventory_settings.address, token=inventory_settings.token)
+
+    # Check for SSL Verification, set it to false if not. Else set to true
+    if not inventory_settings.verify_ssl:
+        # No manual session is required for this, pynautobot will automatically create one
+        nautobot.http_session.verify = False
+    else:
+        nautobot.http_session.verify = True
+
+    # Set a Results dictionary
+    results = {
+        "device": None,
+        "interfaces": None,
+    }
+
+    # Get the device based on the filter
+    device = nautobot.dcim.devices.filter(name=task.host.name)
+
+    # Return a failed that there were too many devices returned in the filterset
+    if len(device) > 1:
+        LOGGER.warning("More than 1 device returned from Nautobot for %s", task.host.name)
+        return Result(host=task.host, failed=True)
+
+    # Return a failed when no devices were returned
+    if not device:
+        LOGGER.warning("No device returned from Nautobot for %s", task.host.name)
+        return Result(host=task.host, failed=True)
+
+    results["device"] = dict(device[0])
+
+    # TODO move the logic to pull the interface and potentially IP here
+    # interfaces = netbox.dcim.interfaces.filter(device=task.host.name)
+    # results["interfaces"] = [ dict(intf) for intf in interfaces ]
+    return Result(host=task.host, result=results)
diff --git a/network_importer/adapters/netbox_api/adapter.py b/network_importer/adapters/netbox_api/adapter.py
index 58e500ae..127379fe 100644
--- a/network_importer/adapters/netbox_api/adapter.py
+++ b/network_importer/adapters/netbox_api/adapter.py
@@ -1,17 +1,4 @@
-"""NetBoxAPIAdapter class.
-
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""NetBoxAPIAdapter class."""
 import logging
 import warnings
 
@@ -34,6 +21,7 @@
     NetboxVlanPre29,
 )
 from network_importer.adapters.netbox_api.tasks import query_device_info_from_netbox
+from network_importer.adapters.netbox_api.settings import InventorySettings, AdapterSettings
 
 warnings.filterwarnings("ignore", category=DeprecationWarning)
 
@@ -59,12 +47,13 @@ class NetBoxAPIAdapter(BaseAdapter):
     netbox = None
     netbox_version = None
 
+    settings_class = AdapterSettings
+
     type = "Netbox"
 
     query_device_info_from_netbox = query_device_info_from_netbox
 
-    @staticmethod
-    def _is_tag_present(netbox_obj):
+    def _is_tag_present(self, netbox_obj):
         """Find if tag is present for a given object."""
         if isinstance(netbox_obj, dict) and not netbox_obj.get("tags", None):  # pylint: disable=no-else-return
             return False
@@ -76,7 +65,7 @@ def _is_tag_present(netbox_obj):
         elif not netbox_obj["tags"]:
             return False
 
-        for tag in config.SETTINGS.netbox.model_flag_tags:
+        for tag in self.settings.model_flag_tags:
             if tag in netbox_obj["tags"]:
                 LOGGER.debug(
                     "Tag (%s) found for object %s. Marked for diffsync flag assignment.", tag, netbox_obj,
@@ -84,12 +73,11 @@ def _is_tag_present(netbox_obj):
                 return True
         return False
 
-    @staticmethod
-    def apply_model_flag(diffsync_obj, netbox_obj):
+    def apply_model_flag(self, diffsync_obj, netbox_obj):
         """Helper function for DiffSync Flag assignment."""
-        model_flag = config.SETTINGS.netbox.model_flag
+        model_flag = self.settings.model_flag
 
-        if model_flag and NetBoxAPIAdapter._is_tag_present(netbox_obj):
+        if model_flag and self._is_tag_present(netbox_obj):
             LOGGER.info(
                 "DiffSync model flag (%s) applied to object %s", model_flag, netbox_obj,
             )
@@ -115,9 +103,10 @@ def _check_netbox_version(self):
 
     def load(self):
         """Initialize pynetbox and load all data from netbox in the local cache."""
-        self.netbox = pynetbox.api(url=config.SETTINGS.netbox.address, token=config.SETTINGS.netbox.token)
+        inventory_settings = InventorySettings(**config.SETTINGS.inventory.settings)
+        self.netbox = pynetbox.api(url=inventory_settings.address, token=inventory_settings.token)
 
-        if not config.SETTINGS.netbox.verify_ssl:
+        if not inventory_settings.verify_ssl:
             session = requests.Session()
             session.verify = False
             self.netbox.http_session = session
diff --git a/network_importer/adapters/netbox_api/config.py b/network_importer/adapters/netbox_api/config.py
deleted file mode 100644
index de0193d5..00000000
--- a/network_importer/adapters/netbox_api/config.py
+++ /dev/null
@@ -1,21 +0,0 @@
-"""Settings definition for the NetboxAPIAdapter.
-
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
-from pydantic import BaseSettings
-
-# pylint: disable=too-few-public-methods,no-self-argument,no-self-use
-
-
-class AdapterSettings(BaseSettings):
-    """Config settings for the netbox_api adapter. Not used currently."""
diff --git a/network_importer/adapters/netbox_api/exceptions.py b/network_importer/adapters/netbox_api/exceptions.py
index 0d058557..6a6cacc2 100644
--- a/network_importer/adapters/netbox_api/exceptions.py
+++ b/network_importer/adapters/netbox_api/exceptions.py
@@ -1,17 +1,4 @@
-"""Custom Exceptions for the NetboxAPIAdapter.
-
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""Custom Exceptions for the NetboxAPIAdapter."""
 
 
 class NetboxObjectNotValid(Exception):
diff --git a/network_importer/adapters/netbox_api/inventory.py b/network_importer/adapters/netbox_api/inventory.py
new file mode 100644
index 00000000..f2ba0ce9
--- /dev/null
+++ b/network_importer/adapters/netbox_api/inventory.py
@@ -0,0 +1,146 @@
+"""Nornir Inventory for netbox."""
+
+# pylint: disable=no-member,too-many-arguments,unused-argument,too-many-branches,too-many-statements
+
+import sys
+from typing import Any, List
+import requests
+import pynetbox
+from pydantic import ValidationError
+
+from nornir.core.inventory import Defaults, Groups, Hosts, Inventory, ParentGroups, ConnectionOptions
+from nornir.core.plugins.inventory import InventoryPluginRegister
+from network_importer.inventory import NetworkImporterInventory, NetworkImporterHost
+from network_importer.utils import build_filter_params
+
+from network_importer.adapters.netbox_api.settings import InventorySettings
+
+
+class NetBoxAPIInventory(NetworkImporterInventory):
+    """Netbox API Inventory Class."""
+
+    def __init__(self, *args, **kwargs: Any,) -> None:
+        """Nornir Inventory Plugin for Netbox API."""
+        super().__init__(*args, **kwargs)
+
+        try:
+            self.settings = InventorySettings(**self.settings)
+        except ValidationError as exc:
+            print(f"Inventory Settings are not valid, found {len(exc.errors())} error(s)")
+            for error in exc.errors():
+                print(f"  inventory/{'/'.join(error['loc'])} | {error['msg']} ({error['type']})")
+            sys.exit(1)
+
+        # Build Filter based on inventory_settings filter and on limit
+        self.filter_parameters = {}
+        if self.settings.filter is not None:
+            build_filter_params(self.settings.filter.split((",")), self.filter_parameters)
+
+        if self.limit:
+            if "=" not in self.limit:
+                self.filter_parameters["name"] = self.limit
+            else:
+                build_filter_params(self.limit.split((",")), self.filter_parameters)
+
+        if "exclude" not in self.filter_parameters.keys():
+            self.filter_parameters["exclude"] = "config_context"
+
+        # Instantiate netbox session using pynetbox
+        self.session = pynetbox.api(url=self.settings.address, token=self.settings.token)
+        if not self.settings.verify_ssl:
+            session = requests.Session()
+            session.verify = False
+            self.session.http_session = session
+
+    def load(self):
+        """Load inventory by fetching devices from netbox."""
+        if self.filter_parameters:
+            devices: List[pynetbox.modules.dcim.Devices] = self.session.dcim.devices.filter(**self.filter_parameters)
+        else:
+            devices: List[pynetbox.modules.dcim.Devices] = self.session.dcim.devices.all()
+
+        # fetch all platforms from Netbox and build mapping:   platform:  napalm_driver
+        platforms = self.session.dcim.platforms.all()
+        platforms_mapping = {platform.slug: platform.napalm_driver for platform in platforms if platform.napalm_driver}
+
+        hosts = Hosts()
+        groups = Groups()
+        defaults = Defaults()
+
+        for dev in devices:
+            # Netbox allows devices to be unnamed, but the Nornir model does not allow this
+            # If a device is unnamed we will set the name to the id of the device in netbox
+            dev_name = dev.name or dev.id
+            host = NetworkImporterHost(name=dev_name, connection_options=ConnectionOptions())
+
+            # Only add virtual chassis master as inventory element
+            if dev.virtual_chassis and dev.virtual_chassis.master:
+                if dev.id != dev.virtual_chassis.master.id:
+                    continue
+                host.data["virtual_chassis"] = True
+
+            else:
+                host.data["virtual_chassis"] = False
+
+            # If supported_platforms is provided
+            # skip all devices that do not match the list of supported platforms
+            # TODO need to see if we can filter when doing the query directly
+            if self.supported_platforms:
+                if not dev.platform:
+                    continue
+
+                if dev.platform.slug not in self.supported_platforms:
+                    continue
+
+            # Add value for IP address
+            if self.settings.use_primary_ip and dev.primary_ip:
+                host.hostname = dev.primary_ip.address.split("/")[0]
+            elif self.settings.use_primary_ip and not dev.primary_ip:
+                host.is_reachable = False
+                host.not_reachable_reason = "primary ip not defined in Netbox"
+            elif not self.settings.use_primary_ip and self.settings.fqdn:
+                host.hostname = f"{dev.name}.{self.settings.fqdn}"
+            elif not self.settings.use_primary_ip:
+                host.hostname = dev.name
+            else:
+                host.hostname = dev_name
+
+            host.site_name = dev.site.slug
+
+            host.data["serial"] = dev.serial
+            host.data["vendor"] = dev.device_type.manufacturer.slug
+            host.data["asset_tag"] = dev.asset_tag
+            host.data["custom_fields"] = dev.custom_fields
+            host.data["site_id"] = dev.site.id
+            host.data["device_id"] = dev.id
+            host.data["role"] = dev.device_role.slug
+            host.data["model"] = dev.device_type.slug
+
+            # Attempt to add 'platform' based of value in 'slug'
+            if dev.platform and dev.platform.slug in platforms_mapping:
+                host.connection_options = {"napalm": ConnectionOptions(platform=platforms_mapping[dev.platform.slug])}
+
+            if dev.platform:
+                host.platform = dev.platform.slug
+            else:
+                host.platform = None
+
+            host.groups = ParentGroups([self.global_group])
+
+            if dev.site.slug not in groups.keys():
+                groups[dev.site.slug] = {}
+
+            if dev.device_role.slug not in groups.keys():
+                groups[dev.device_role.slug] = {}
+
+            if host.hostname and host.platform:
+                host.is_reachable = True
+
+            # Assign temporary dict to outer dict
+
+            hosts[dev_name] = host
+
+        return Inventory(hosts=hosts, groups=groups, defaults=defaults)
+
+
+InventoryPluginRegister.register("NetBoxAPIInventory", NetBoxAPIInventory)
diff --git a/network_importer/adapters/netbox_api/models.py b/network_importer/adapters/netbox_api/models.py
index 45d33f16..24dcf74c 100644
--- a/network_importer/adapters/netbox_api/models.py
+++ b/network_importer/adapters/netbox_api/models.py
@@ -1,17 +1,4 @@
-"""Extension of the base Models for the NetboxAPIAdapter.
-
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""Extension of the base Models for the NetboxAPIAdapter."""
 from typing import Optional
 import logging
 
diff --git a/network_importer/adapters/netbox_api/settings.py b/network_importer/adapters/netbox_api/settings.py
new file mode 100644
index 00000000..a7d27cd7
--- /dev/null
+++ b/network_importer/adapters/netbox_api/settings.py
@@ -0,0 +1,33 @@
+"""Settings definition for the NetboxAPIAdapter."""
+from typing import List, Union, Optional
+from pydantic import BaseSettings
+
+from diffsync import DiffSyncModelFlags
+
+
+class AdapterSettings(BaseSettings):
+    """Config settings for the netbox_api adapter. Not used currently."""
+
+    model_flag_tags: List[str] = list()  # List of tags that defines what objects to assign the model_flag to.
+    model_flag: Optional[DiffSyncModelFlags]  # The model flag that will be applied to objects based on tag.
+
+
+class InventorySettings(BaseSettings):
+    """Config settings for the NetboxAPI inventory."""
+
+    address: Optional[str] = "http://localhost"
+    token: Optional[str] = None
+    verify_ssl: Union[bool, str] = True
+
+    use_primary_ip: Optional[bool] = True
+    fqdn: Optional[str] = None
+    filter: Optional[str] = ""
+
+    class Config:
+        """Additional parameters to automatically map environment variable to some settings."""
+
+        fields = {
+            "address": {"env": "NETBOX_ADDRESS"},
+            "token": {"env": "NETBOX_TOKEN"},
+            "verify_ssl": {"env": "NETBOX_VERIFY_SSL"},
+        }
diff --git a/network_importer/adapters/netbox_api/tasks.py b/network_importer/adapters/netbox_api/tasks.py
index 80eadca2..e352600d 100644
--- a/network_importer/adapters/netbox_api/tasks.py
+++ b/network_importer/adapters/netbox_api/tasks.py
@@ -1,17 +1,4 @@
-"""Collection of nornir tasks for the NetboxAPIAdapter.
-
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""Collection of nornir tasks for the NetboxAPIAdapter."""
 import logging
 
 import pynetbox
@@ -20,6 +7,8 @@
 
 import network_importer.config as config  # pylint: disable=import-error
 
+from network_importer.adapters.netbox_api.settings import InventorySettings
+
 LOGGER = logging.getLogger("network-importer")
 
 
@@ -37,9 +26,10 @@ def query_device_info_from_netbox(task: Task) -> Result:
     Returns:
         Result: Nornir Result object with the result in a dict format
     """
-    netbox = pynetbox.api(url=config.SETTINGS.netbox.address, token=config.SETTINGS.netbox.token)
+    inventory_settings = InventorySettings(**config.SETTINGS.inventory.settings)
+    netbox = pynetbox.api(url=inventory_settings.address, token=inventory_settings.token)
 
-    if not config.SETTINGS.netbox.verify_ssl:
+    if not inventory_settings.verify_ssl:
         session = requests.Session()
         session.verify = False
         netbox.http_session = session
diff --git a/network_importer/adapters/network_importer/adapter.py b/network_importer/adapters/network_importer/adapter.py
index aad2912a..570f1b17 100644
--- a/network_importer/adapters/network_importer/adapter.py
+++ b/network_importer/adapters/network_importer/adapter.py
@@ -1,17 +1,4 @@
-"""Custom Exceptions for the NetworkImporterAdapter.
-
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""Custom Exceptions for the NetworkImporterAdapter."""
 import re
 import ipaddress
 import json
@@ -62,20 +49,20 @@ def load(self):
         for hostname, host in self.nornir.inventory.hosts.items():
 
             if len(self.bfi.q.nodeProperties(nodes=f'"{hostname}"').answer()) == 0:
-                self.nornir.inventory.hosts[hostname].data["has_config"] = False
+                self.nornir.inventory.hosts[hostname].has_config = False
                 LOGGER.warning("Unable to find information for %s in Batfish, SKIPPING", hostname)
                 continue
 
-            self.nornir.inventory.hosts[hostname].data["has_config"] = True
+            self.nornir.inventory.hosts[hostname].has_config = True
 
-            if host.data["site"] not in sites.keys():
-                site = self.site(name=host.data["site"])
-                sites[host.data["site"]] = site
+            if host.site_name not in sites.keys():
+                site = self.site(name=host.site_name)
+                sites[host.site_name] = site
                 self.add(site)
             else:
-                site = sites[host.data["site"]]
+                site = sites[host.site_name]
 
-            device = self.device(name=hostname, site_name=host.data["site"])
+            device = self.device(name=hostname, site_name=host.site_name)
             self.add(device)
 
         if config.SETTINGS.main.import_cabling in ["lldp", "cdp"] or config.SETTINGS.main.import_vlans in [True, "cli"]:
diff --git a/network_importer/adapters/network_importer/exceptions.py b/network_importer/adapters/network_importer/exceptions.py
index e04bc529..f9fb256f 100644
--- a/network_importer/adapters/network_importer/exceptions.py
+++ b/network_importer/adapters/network_importer/exceptions.py
@@ -1,17 +1,4 @@
-"""Custom Exceptions for the NetworkImporterAdapter.
-
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""Custom Exceptions for the NetworkImporterAdapter."""
 
 
 class BatfishObjectNotValid(Exception):
diff --git a/network_importer/cli.py b/network_importer/cli.py
index 571ab704..58472f14 100644
--- a/network_importer/cli.py
+++ b/network_importer/cli.py
@@ -46,7 +46,7 @@ def main():
 
 def init(config_file):
     """Init Network-Importer."""
-    config.load(config_file_name=config_file)
+    config.load_and_exit(config_file_name=config_file)
     perf.init()
 
     # ------------------------------------------------------------
@@ -192,12 +192,12 @@ def inventory(config_file, limit, debug, update_configs):
     table.add_column("Reason")
 
     for hostname, host in ni.nornir.inventory.hosts.items():
-        if host.data["is_reachable"]:
+        if host.is_reachable:
             is_reachable = "[green]True"
             reason = None
         else:
             is_reachable = "[red]False"
-            reason = f"[red]{host.data['not_reachable_reason']}"
+            reason = f"[red]{host.not_reachable_reason}"
 
         driver = config.SETTINGS.drivers.mapping.get(host.platform, config.SETTINGS.drivers.mapping.get("default"))
         table.add_row(hostname, host.platform, driver, is_reachable, reason)
@@ -205,6 +205,12 @@ def inventory(config_file, limit, debug, update_configs):
     console = Console()
     console.print(table)
 
+    if update_configs:
+        ni.update_configurations()
+
+    if debug:
+        pdb.set_trace()
+
 
 if __name__ == "__main__":
     main()
diff --git a/network_importer/config.py b/network_importer/config.py
index 9d940933..e176f601 100644
--- a/network_importer/config.py
+++ b/network_importer/config.py
@@ -1,17 +1,4 @@
-"""Settings definition for the network importer.
-
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""Settings definition for the network importer."""
 # pylint: disable=invalid-name,redefined-outer-name
 
 import os
@@ -22,7 +9,8 @@
 import toml
 from pydantic import BaseSettings, ValidationError
 from typing_extensions import Literal
-from diffsync import DiffSyncModelFlags
+
+from network_importer.exceptions import ConfigLoadFatalError
 
 SETTINGS = None
 
@@ -35,7 +23,18 @@
     "arista_eos": "network_importer.drivers.arista_eos",
 }
 
-# pylint: disable=too-few-public-methods,global-statement
+DEFAULT_BACKENDS = {
+    "netbox": {
+        "inventory": "NetBoxAPIInventory",
+        "adapter": "network_importer.adapters.netbox_api.adapter.NetBoxAPIAdapter",
+    },
+    "nautobot": {
+        "inventory": "NautobotAPIInventory",
+        "adapter": "network_importer.adapters.nautobot_api.adapter.NautobotAPIAdapter",
+    },
+}
+
+# pylint: disable=global-statement
 
 
 class BatfishSettings(BaseSettings):
@@ -58,43 +57,16 @@ class Config:
         }
 
 
-class NetboxSettings(BaseSettings):
-    """Settings definition for the Netbox section of the configuration."""
-
-    address: str = "http://localhost"
-    token: Optional[str]
-    verify_ssl: bool = True
-
-    model_flag_tags: List[str] = list()  # List of tags that defines what objects to assign the model_flag to.
-    model_flag: Optional[DiffSyncModelFlags]  # The model flag that will be applied to objects based on tag.
-
-    """Define a list of supported platform,
-    if defined all devices without platform or with a different platforms will be removed from the inventory"""
-    supported_platforms: List[str] = list()
-
-    # Currently not used in 2.x, need to add them back
-    # cacert: Optional[str]
-    # request_ssl_verify: bool = False
-
-    class Config:
-        """Additional parameters to automatically map environment variable to some settings."""
-
-        fields = {
-            "address": {"env": "NETBOX_ADDRESS"},
-            "token": {"env": "NETBOX_TOKEN"},
-            "verify_ssl": {"env": "NETBOX_VERIFY_SSL"},
-        }
-
-
 class NetworkSettings(BaseSettings):
     """Settings definition for the Network section of the configuration."""
 
     login: Optional[str]
     password: Optional[str]
     enable: bool = True
-    global_delay_factor: int = 5
-    banner_timeout: int = 15
-    conn_timeout: int = 5
+
+    netmiko_extras: Optional[dict]
+    napalm_extras: Optional[dict]
+
     fqdns: List[str] = list()  # List of valid FQDN that can be found in the network
 
     class Config:
@@ -136,6 +108,10 @@ class MainSettings(BaseSettings):
 
     configs_directory: str = "configs"
 
+    backend: Optional[Literal["nautobot", "netbox"]]
+    """Only Netbox and Nautobot backend are included by default, if you want to use another backend
+    you must leave backend empty and define inventory.inventory_class and adapters.sot_class manually."""
+
     # NOT SUPPORTED CURRENTLY
     generate_hostvars: bool = False
     hostvars_directory: str = "host_vars"
@@ -145,9 +121,10 @@ class AdaptersSettings(BaseSettings):
     """Settings definition for the Adapters section of the configuration."""
 
     network_class: str = "network_importer.adapters.network_importer.adapter.NetworkImporterAdapter"
-    sot_class: str = "network_importer.adapters.netbox_api.adapter.NetBoxAPIAdapter"
-    sot_params: Optional[dict]
-    network_params: Optional[dict]
+    network_settings: Optional[dict]
+
+    sot_class: Optional[str]
+    sot_settings: Optional[dict]
 
 
 class DriversSettings(BaseSettings):
@@ -163,16 +140,10 @@ class InventorySettings(BaseSettings):
     if the use_primary_ip flag is disabled, the inventory will try to use the hostname to the device
     """
 
-    use_primary_ip: bool = True
-    fqdn: Optional[str]
-
-    inventory_class: str = "network_importer.inventory.NetboxInventory"
-    filter: str = ""
+    inventory_class: Optional[str]
+    settings: Optional[dict]
 
-    class Config:
-        """Additional parameters to automatically map environment variable to some settings."""
-
-        fields = {"inventory_filter": {"env": "INVENTORY_FILTER"}}
+    supported_platforms: List[str] = list()
 
 
 class Settings(BaseSettings):
@@ -183,7 +154,6 @@ class Settings(BaseSettings):
     """
 
     main: MainSettings = MainSettings()
-    netbox: NetboxSettings = NetboxSettings()
     batfish: BatfishSettings = BatfishSettings()
     logs: LogsSettings = LogsSettings()
     network: NetworkSettings = NetworkSettings()
@@ -192,33 +162,83 @@ class Settings(BaseSettings):
     inventory: InventorySettings = InventorySettings()
 
 
+def _configure_backend(settings: Settings):
+    """Configure the inventory and the SOT adapter for a given backend.
+
+    The inventory and/or the adapter will be updated if:
+      - a backend provided is valid
+      - no value has been defined already
+
+    Args:
+        settings (Settings)
+
+    Returns:
+        Settings
+    """
+    if not settings.main.backend and (not settings.inventory.inventory_class or not settings.adapters.sot_class):
+        raise ConfigLoadFatalError(
+            "You must define a valid backend or assign inventory.inventory_class and adapters.sot_class manually."
+        )
+
+    if not settings.main.backend:
+        return settings
+
+    supported_backends = DEFAULT_BACKENDS.keys()
+    if settings.main.backend not in supported_backends:
+        raise ConfigLoadFatalError(f"backend value one of : {', '.join(supported_backends)}")
+
+    if not settings.inventory.inventory_class:
+        settings.inventory.inventory_class = DEFAULT_BACKENDS[settings.main.backend]["inventory"]
+
+    if not settings.adapters.sot_class:
+        settings.adapters.sot_class = DEFAULT_BACKENDS[settings.main.backend]["adapter"]
+
+    return settings
+
+
 def load(config_file_name="network_importer.toml", config_data=None):
-    """Load a configuration file in pyproject.toml format that contains the settings.
+    """Load configuration.
 
-    The settings for this app are expected to be in [tool.json_schema_testing] in TOML
-    if nothing is found in the config file or if the config file do not exist, the default values will be used.
+    Configuration is loaded from a file in network_importer.toml format that contains the settings,
+    or from a dictionary of those settings passed in as "config_data"
 
     Args:
-        config_file_name (str, optional): Name of the configuration file to load. Defaults to "pyproject.toml".
+        config_file_name (str, optional): Name of the configuration file to load. Defaults to "network_importer.toml".
         config_data (dict, optional): dict to load as the config file instead of reading the file. Defaults to None.
     """
     global SETTINGS
 
     if config_data:
-        SETTINGS = Settings(**config_data)
+        SETTINGS = _configure_backend(Settings(**config_data))
         return
 
     if os.path.exists(config_file_name):
         config_string = Path(config_file_name).read_text()
         config_tmp = toml.loads(config_string)
-
-        try:
-            SETTINGS = Settings(**config_tmp)
-            return
-        except ValidationError as e:
-            print(f"Configuration not valid, found {len(e.errors())} error(s)")
-            for error in e.errors():
-                print(f"  {'/'.join(error['loc'])} | {error['msg']} ({error['type']})")
-            sys.exit(1)
+        SETTINGS = _configure_backend(Settings(**config_tmp))
+        return
 
     SETTINGS = Settings()
+
+
+def load_and_exit(config_file_name="network_importer.toml", config_data=None):
+    """Calls load, but wraps it in a try except block.
+
+    This is done to handle a ValidationError which is raised when settings are specified but invalid.
+    In such cases, a message is printed to the screen indicating the settings which don't pass validation.
+
+    Args:
+        config_file_name (str, optional): [description]. Defaults to "network_importer.toml".
+        config_data (dict, optional): [description]. Defaults to None.
+    """
+    try:
+        load(config_file_name=config_file_name, config_data=config_data)
+    except ValidationError as err:
+        print(f"Configuration not valid, found {len(err.errors())} error(s)")
+        for error in err.errors():
+            print(f"  {'/'.join(error['loc'])} | {error['msg']} ({error['type']})")
+        sys.exit(1)
+    except ConfigLoadFatalError as err:
+        print("Configuration not valid")
+        print(f"  {err}")
+        sys.exit(1)
diff --git a/network_importer/drivers/__init__.py b/network_importer/drivers/__init__.py
index 3ae9e569..e5a72aa8 100644
--- a/network_importer/drivers/__init__.py
+++ b/network_importer/drivers/__init__.py
@@ -1,16 +1,4 @@
-"""
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""Main dispatcher for nornir."""
 import logging
 import importlib
 
diff --git a/network_importer/drivers/cisco_default.py b/network_importer/drivers/cisco_default.py
index 03906db0..ed64c946 100644
--- a/network_importer/drivers/cisco_default.py
+++ b/network_importer/drivers/cisco_default.py
@@ -14,7 +14,7 @@
 """
 import logging
 
-from nornir.plugins.tasks.networking import netmiko_send_command
+from nornir_netmiko.tasks import netmiko_send_command
 from nornir.core.task import Result, Task
 from nornir.core.exceptions import NornirSubTaskError
 
diff --git a/network_importer/drivers/default.py b/network_importer/drivers/default.py
index 8aa52510..43e1ca3f 100644
--- a/network_importer/drivers/default.py
+++ b/network_importer/drivers/default.py
@@ -13,7 +13,10 @@
 limitations under the License.
 """
 import logging
-from nornir.plugins.tasks.networking import napalm_get, netmiko_send_command
+
+from nornir_napalm.plugins.tasks import napalm_get
+from nornir_netmiko.tasks import netmiko_send_command
+
 from nornir.core.task import Result, Task
 from nornir.core.exceptions import NornirSubTaskError
 
diff --git a/network_importer/exceptions.py b/network_importer/exceptions.py
index a0d92ab9..ce64f786 100644
--- a/network_importer/exceptions.py
+++ b/network_importer/exceptions.py
@@ -1,18 +1,9 @@
-"""Custom Exceptions for the NetworkImporterAdapter.
+"""Custom Exceptions for the NetworkImporterAdapter."""
 
-(c) 2020 Network To Code
 
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+class AdapterLoadFatalError(Exception):
+    """Network Importer Exception."""
 
 
-class AdapterLoadFatalError(Exception):
+class ConfigLoadFatalError(Exception):
     """Network Importer Exception."""
diff --git a/network_importer/inventory.py b/network_importer/inventory.py
index b72d862f..71997644 100644
--- a/network_importer/inventory.py
+++ b/network_importer/inventory.py
@@ -1,200 +1,94 @@
-"""Norning Inventory for netbox.
+"""Base Inventory and Host class for Network Importer."""
 
-(c) 2020 Network To Code
+from typing import Dict, List, Optional
 
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
-# Disable too-many-arguments and too-many-locals pylint tests for this file. These are both necessary
-# pylint: disable=R0913,R0914,E1101,W0613
+from nornir.core.inventory import Host, Group, ConnectionOptions
 
-import copy
-from typing import Any, Dict, List, Optional, Union
+# pylint: disable=too-many-arguments,too-many-instance-attributes
 
-import requests
-import pynetbox
 
-from nornir.core.deserializer.inventory import Inventory, HostsDict
+class NetworkImporterHost(Host):
+    """Network Importer Host Class."""
 
-# import network_importer.config as config
+    site_name: Optional[str]
+    """Name of the site this device belong to."""
 
-# ------------------------------------------------------------
-# Network Importer Base Dict for device data
-#   status:
-#     ok: device is reachable
-#     fail-ip: Primary IP address not reachable
-#     fail-access: Unable to access the device management. The IP is reachable, but SSH or API is not enabled or
-#                  responding.
-#     fail-login: Unable to login authenticate with device
-#     fail-other:  Other general processing error (also catches traps/bug)
-#   is_reachable: Global Flag to indicate if we are able to connect to a device
-#   has_config: Indicate if the configuration is present and has been properly imported in Batfish
-# ------------------------------------------------------------
+    is_reacheable: Optional[bool]
+    """Global Flag to indicate if we are able to connect to a device"""
 
-BASE_DATA = {"is_reachable": None, "status": "ok", "has_config": False}
+    status: Optional[str] = "ok"
+    """ Valid Statuses
+        ok: device is reachable
+        fail-ip: Primary IP address not reachable
+        fail-access: Unable to access the device management.
+                     The IP is reachable, but SSH or API is not enabled or responding.
+        fail-login: Unable to login authenticate with device
+        fail-other:  Other general processing error (also catches traps/bug)
+    """
+
+    has_config: Optional[bool] = False
+    """ Indicate if the configuration is present and has been properly imported in Batfish."""
+
+    not_reachable_reason: Optional[str]
 
 
-class NetboxInventory(Inventory):
-    """Netbox Inventory Class."""
+class NetworkImporterInventory:
+    """Base inventory class for the Network Importer."""
 
-    # pylint: disable=dangerous-default-value, too-many-branches, too-many-statements
     def __init__(
         self,
-        nb_url: Optional[str] = None,
-        nb_token: Optional[str] = None,
-        ssl_verify: Union[bool, str] = True,
         username: Optional[str] = None,
         password: Optional[str] = None,
-        enable: Optional[bool] = True,
-        use_primary_ip: Optional[bool] = True,
-        fqdn: Optional[str] = None,
-        supported_platforms: Optional[List[str]] = [],
-        filter_parameters: Optional[Dict[str, Any]] = None,
-        global_delay_factor: Optional[int] = 5,
-        banner_timeout: Optional[int] = 15,
-        conn_timeout: Optional[int] = 5,
-        **kwargs: Any,
-    ) -> None:
-        """Norning Inventory Plugin fir Netbox.
-
-        Hard copy from https://github.com/nornir-automation/nornir/blob/develop/nornir/plugins/inventory/netbox.py,
-        Need to see how to contribute back some of these modifications
-
-        Args:
-          filter_parameters: Key
-          nb_url: Optional[str]:  (Default value = None)
-          nb_token: Optional[str]:  (Default value = None)
-          ssl_verify: (Default value = True)
-          filter_parameters: Optional[Dict[str: Any]]:  (Default value = None)
-          username: Optional[str]
-          password: Optional[str]
-          enable: Optional[bool] = True,
-          use_primary_ip: Optional[bool] = True,
-          fqdn: Optional[str] = None,
-          supported_platforms: Optional[List[str]]
-          global_delay_factor: Optional[int] Global Delay factor for netmiko
-          banner_timeout: Optional[int] Banner Timeout for netmiko/paramiko
-          conn_timeout: Optional[int] Connection timeout for netmiko/paramiko
-          **kwargs: Any:
-        """
-        filter_parameters = filter_parameters or {}
-
-        if "exclude" not in filter_parameters.keys():
-            filter_parameters["exclude"] = "config_context"
-
-        # Instantiate netbox session using pynetbox
-        nb_session = pynetbox.api(url=nb_url, token=nb_token)
-        if not ssl_verify:
-            session = requests.Session()
-            session.verify = False
-            nb_session.http_session = session
-
-        # fetch devices from netbox
-        if filter_parameters:
-            nb_devices: List[pynetbox.modules.dcim.Devices] = nb_session.dcim.devices.filter(**filter_parameters)
-        else:
-            nb_devices: List[pynetbox.modules.dcim.Devices] = nb_session.dcim.devices.all()
-
-        # fetch all platforms from Netbox and build mapping:   platform:  napalm_driver
-        platforms = nb_session.dcim.platforms.all()
-        platforms_mapping = {platform.slug: platform.napalm_driver for platform in platforms if platform.napalm_driver}
-
-        hosts = {}
-        groups = {"global": {"connection_options": {"netmiko": {"extras": {}}, "napalm": {"extras": {}}}}}
+        enable: Optional[bool] = None,
+        supported_platforms: Optional[List[str]] = None,
+        netmiko_extras: Optional[Dict] = None,
+        napalm_extras: Optional[Dict] = None,
+        limit: Optional[str] = None,
+        settings: Optional[Dict] = None,
+    ):
+        """Initialize class and store all top level arguments locally."""
+        self.username = username
+        self.password = password
+        self.enable = enable
+        self.supported_platforms = supported_platforms
+        self.limit = limit
+        self.netmiko_extras = netmiko_extras
+        self.napalm_extras = napalm_extras
+
+        self.settings = settings
+
+        # Define Global Group with Netmiko and Napalm Credentials if provided
+        self.global_group = Group(
+            name="global", connection_options={"netmiko": ConnectionOptions(), "napalm": ConnectionOptions()}
+        )
+
+        if self.netmiko_extras:
+            self.global_group.connection_options["netmiko"].extras = self.netmiko_extras
+
+        if self.napalm_extras:
+            self.global_group.connection_options["napalm"].extras = self.napalm_extras
 
         # Pull the login and password from the NI config object if available
-        if username:
-            groups["global"]["username"] = username
-
-        if password:
-            groups["global"]["password"] = password
-            if enable:
-                groups["global"]["connection_options"]["netmiko"]["extras"] = {
-                    "secret": password,
-                    "global_delay_factor": global_delay_factor,
-                    "banner_timeout": banner_timeout,
-                    "conn_timeout": conn_timeout,
-                }
-                groups["global"]["connection_options"]["napalm"]["extras"] = {"optional_args": {"secret": password}}
-
-        for dev in nb_devices:
-
-            host: HostsDict = {"data": copy.deepcopy(BASE_DATA)}
-
-            # Only add virtual chassis master as inventory element
-            if dev.virtual_chassis and dev.virtual_chassis.master:
-                if dev.id != dev.virtual_chassis.master.id:
-                    continue
-                host["data"]["virtual_chassis"] = True
-
-            else:
-                host["data"]["virtual_chassis"] = False
-
-            # If supported_platforms is provided
-            # skip all devices that do not match the list of supported platforms
-            # TODO need to see if we can filter when doing the query directly
-            if supported_platforms:
-                if not dev.platform:
-                    continue
-
-                if dev.platform.slug not in supported_platforms:
-                    continue
-
-            # Add value for IP address
-            if use_primary_ip and dev.primary_ip:
-                host["hostname"] = dev.primary_ip.address.split("/")[0]
-            elif use_primary_ip and not dev.primary_ip:
-                host["data"]["is_reachable"] = False
-                host["data"]["not_reachable_reason"] = "primary ip not defined in Netbox"
-            elif not use_primary_ip and fqdn:
-                host["hostname"] = f"{dev.name}.{fqdn}"
-            elif not use_primary_ip:
-                host["hostname"] = dev.name
-
-            host["data"]["serial"] = dev.serial
-            host["data"]["vendor"] = dev.device_type.manufacturer.slug
-            host["data"]["asset_tag"] = dev.asset_tag
-            host["data"]["custom_fields"] = dev.custom_fields
-            host["data"]["site"] = dev.site.slug
-            host["data"]["site_id"] = dev.site.id
-            host["data"]["device_id"] = dev.id
-            host["data"]["role"] = dev.device_role.slug
-            host["data"]["model"] = dev.device_type.slug
-
-            # Attempt to add 'platform' based of value in 'slug'
-            if dev.platform and dev.platform.slug in platforms_mapping:
-                host["connection_options"] = {"napalm": {"platform": platforms_mapping[dev.platform.slug]}}
-
-            if dev.platform:
-                host["platform"] = dev.platform.slug
-            else:
-                host["platform"] = None
-
-            host["groups"] = ["global", dev.site.slug, dev.device_role.slug]
-
-            if dev.site.slug not in groups.keys():
-                groups[dev.site.slug] = {}
-
-            if dev.device_role.slug not in groups.keys():
-                groups[dev.device_role.slug] = {}
-
-            if "hostname" in host and host["hostname"] and "platform" in host and host["platform"]:
-                host["data"]["is_reachable"] = True
-
-            # Assign temporary dict to outer dict
-            # Netbox allows devices to be unnamed, but the Nornir model does not allow this
-            # If a device is unnamed we will set the name to the id of the device in netbox
-            hosts[dev.name or dev.id] = host
-
-        # Pass the data back to the parent class
-        super().__init__(hosts=hosts, groups=groups, defaults={}, **kwargs)
+        if self.username:
+            self.global_group.username = self.username
+
+        if self.password:
+            self.global_group.password = self.password
+            if self.enable:
+                if not self.global_group.connection_options["netmiko"].extras:
+                    self.global_group.connection_options["netmiko"].extras = dict()
+                elif "secret" not in self.global_group.connection_options["netmiko"].extras:
+                    self.global_group.connection_options["netmiko"].extras["secret"] = self.password
+
+                if not self.global_group.connection_options["napalm"].extras:
+                    self.global_group.connection_options["napalm"].extras = {"optional_args": {"secret": self.password}}
+                elif isinstance(dict, self.global_group.connection_options["napalm"].extras):
+                    if "optional_args" not in self.global_group.connection_options["napalm"].extras:
+                        self.global_group.connection_options["napalm"].extras["optional_args"] = {
+                            "secret": self.password
+                        }
+                    elif isinstance(dict, self.global_group.connection_options["napalm"].extras["optional_args"]):
+                        self.global_group.connection_options["napalm"].extras["optional_args"]["secret"] = self.password
 
 
 # -----------------------------------------------------------------
@@ -211,7 +105,7 @@ def valid_devs(host):
     Returns:
         bool: True if the device has a config, False otherwise.
     """
-    if host.data["has_config"]:
+    if host.has_config:
         return True
 
     return False
@@ -228,7 +122,7 @@ def non_valid_devs(host):
     Returns:
         bool: True if the device do not have a config, False otherwise.
     """
-    if host.data["has_config"]:
+    if host.has_config:
         return False
 
     return True
@@ -245,7 +139,7 @@ def reachable_devs(host):
     Returns:
         bool: True if the device is reachable, False otherwise.
     """
-    if host.data["is_reachable"]:
+    if host.is_reachable:
         return True
 
     return False
@@ -262,7 +156,7 @@ def non_reachable_devs(host):
     Returns:
         bool: True if the device is not reachable, False otherwise.
     """
-    if host.data["is_reachable"]:
+    if host.is_reachable:
         return False
 
     return True
@@ -279,7 +173,7 @@ def valid_and_reachable_devs(host):
     Returns:
         bool: True if the device is reachable and has a config, False otherwise.
     """
-    if host.data["is_reachable"] and host.data["has_config"]:
+    if host.is_reachable and host.has_config:
         return True
 
     return False
diff --git a/network_importer/main.py b/network_importer/main.py
index d52389c5..7ebdaf14 100644
--- a/network_importer/main.py
+++ b/network_importer/main.py
@@ -1,26 +1,15 @@
-"""main class for the network importer.
-
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""main class for the network importer."""
 import logging
 import os
 import sys
 import warnings
 import importlib
 
+from pydantic import ValidationError
+
 import network_importer.config as config
 from network_importer.exceptions import AdapterLoadFatalError
-from network_importer.utils import patch_http_connection_pool, build_filter_params
+from network_importer.utils import patch_http_connection_pool
 from network_importer.processors.get_config import GetConfig
 from network_importer.drivers import dispatcher
 from network_importer.diff import NetworkImporterDiff
@@ -33,9 +22,11 @@
 with warnings.catch_warnings():
     warnings.filterwarnings("ignore", category=DeprecationWarning)
     from nornir import InitNornir
+    from nornir.core.plugins.inventory import InventoryPluginRegister
 
 __author__ = "Damien Garros <damien.garros@networktocode.com>"
 
+
 LOGGER = logging.getLogger("network-importer")
 
 
@@ -52,35 +43,31 @@ def __init__(self, check_mode=False, nornir=None):
     @timeit
     def build_inventory(self, limit=None):
         """Build the inventory for the Network Importer in Nornir format."""
-        # Filters can be defined at the configuration level or in CLI or both
-        params = {}
-        build_filter_params(config.SETTINGS.inventory.filter.split((",")), params)
-        if limit:
-            if "=" not in limit:
-                params["name"] = limit
-            else:
-                build_filter_params(limit.split((",")), params)
-
-        # TODO Cleanup config file and allow user defined inventory
+        # pylint: disable=import-outside-toplevel
+        # Load build-in Inventories as needed
+        if config.SETTINGS.inventory.inventory_class == "NetBoxAPIInventory":
+            from network_importer.adapters.netbox_api.inventory import NetBoxAPIInventory
+
+            InventoryPluginRegister.register("NetBoxAPIInventory", NetBoxAPIInventory)
+        elif config.SETTINGS.inventory.inventory_class == "NautobotAPIInventory":
+            from network_importer.adapters.nautobot_api.inventory import NautobotAPIInventory
+
+            InventoryPluginRegister.register("NautobotAPIInventory", NautobotAPIInventory)
+
         self.nornir = InitNornir(
-            core={"num_workers": config.SETTINGS.main.nbr_workers},
+            runner={"plugin": "threaded", "options": {"num_workers": config.SETTINGS.main.nbr_workers}},
             logging={"enabled": False},
             inventory={
                 "plugin": config.SETTINGS.inventory.inventory_class,
                 "options": {
-                    "nb_url": config.SETTINGS.netbox.address,
-                    "nb_token": config.SETTINGS.netbox.token,
-                    "filter_parameters": params,
-                    "ssl_verify": config.SETTINGS.netbox.verify_ssl,
                     "username": config.SETTINGS.network.login,
                     "password": config.SETTINGS.network.password,
                     "enable": config.SETTINGS.network.enable,
-                    "use_primary_ip": config.SETTINGS.inventory.use_primary_ip,
-                    "fqdn": config.SETTINGS.inventory.fqdn,
-                    "supported_platforms": config.SETTINGS.netbox.supported_platforms,
-                    "global_delay_factor": config.SETTINGS.network.global_delay_factor,
-                    "banner_timeout": config.SETTINGS.network.banner_timeout,
-                    "conn_timeout": config.SETTINGS.network.conn_timeout,
+                    "supported_platforms": config.SETTINGS.inventory.supported_platforms,
+                    "netmiko_extras": config.SETTINGS.network.netmiko_extras,
+                    "napalm_extras": config.SETTINGS.network.napalm_extras,
+                    "limit": limit,
+                    "settings": config.SETTINGS.inventory.settings,
                 },
             },
         )
@@ -115,25 +102,35 @@ def init(self, limit=None):
         # --------------------------------------------------------
         LOGGER.info("Import SOT Model")
         sot_path = config.SETTINGS.adapters.sot_class.split(".")
-        sot_params = config.SETTINGS.adapters.sot_params
+        sot_settings = config.SETTINGS.adapters.sot_settings
         sot_adapter = getattr(importlib.import_module(".".join(sot_path[0:-1])), sot_path[-1])
 
         try:
-            self.sot = sot_adapter(nornir=self.nornir, config=sot_params)
+            self.sot = sot_adapter(nornir=self.nornir, settings=sot_settings)
             self.sot.load()
+        except ValidationError as exc:
+            print(f"Configuration not valid, found {len(exc.errors())} error(s)")
+            for error in exc.errors():
+                print(f"  {'/'.join(error['loc'])} | {error['msg']} ({error['type']})")
+            sys.exit(1)
         except AdapterLoadFatalError as exc:
             LOGGER.error("Unable to load the SOT Adapter : %s", exc)
             sys.exit(1)
 
         LOGGER.info("Import Network Model")
         network_adapter_path = config.SETTINGS.adapters.network_class.split(".")
-        network_adapter_params = config.SETTINGS.adapters.network_params
+        network_adapter_settings = config.SETTINGS.adapters.network_settings
         network_adapter = getattr(
             importlib.import_module(".".join(network_adapter_path[0:-1])), network_adapter_path[-1]
         )
         try:
-            self.network = network_adapter(nornir=self.nornir, config=network_adapter_params)
+            self.network = network_adapter(nornir=self.nornir, settings=network_adapter_settings)
             self.network.load()
+        except ValidationError as exc:
+            print(f"Configuration not valid, found {len(exc.errors())} error(s)")
+            for error in exc.errors():
+                print(f"  {'/'.join(error['loc'])} | {error['msg']} ({error['type']})")
+            sys.exit(1)
         except AdapterLoadFatalError as exc:
             LOGGER.error("Unable to load the SOT Adapter : %s", exc)
             sys.exit(1)
diff --git a/network_importer/processors/get_config.py b/network_importer/processors/get_config.py
index b1c1d105..e99d4cb1 100644
--- a/network_importer/processors/get_config.py
+++ b/network_importer/processors/get_config.py
@@ -111,21 +111,21 @@ def subtask_instance_completed(self, task: Task, host: Host, result: MultiResult
                 LOGGER.warning("%s | %s", task.host.name, result[0].exception)
             else:
                 LOGGER.warning("%s | Something went wrong while trying to update the configuration ", task.host.name)
-            host.data["status"] = "fail-other"
+            host.status = "fail-other"
             return
 
         conf = result[0].result.get("config", None)
 
         if not conf:
             LOGGER.warning("%s | No configuration return ", task.host.name)
-            host.data["status"] = "fail-other"
+            host.status = "fail-other"
             return
 
         # Count the number of lines in the config file, if less than 10 report an error
         # mostlikely something went wrong while pulling the config
         if conf.count("\n") < 10:
             LOGGER.warning("%s | Less than 10 configuration lines returned", task.host.name)
-            host.data["status"] = "fail-other"
+            host.status = "fail-other"
             return
 
         if host.name in self.existing_config_hostnames:
@@ -135,7 +135,7 @@ def subtask_instance_completed(self, task: Task, host: Host, result: MultiResult
         with open(self.config_filename[host.name], "w") as config_:
             config_.write(conf)
 
-        host.data["has_config"] = True
+        host.has_config = True
 
         self.current_md5[host.name] = hashlib.md5(conf.encode("utf-8")).hexdigest()
         # changed = False
diff --git a/network_importer/processors/get_neighbors.py b/network_importer/processors/get_neighbors.py
index 73f8fe57..ad6366d2 100644
--- a/network_importer/processors/get_neighbors.py
+++ b/network_importer/processors/get_neighbors.py
@@ -32,8 +32,6 @@
 # Match the incorrectly capitalized interface names
 JUNOS_INTERFACE_PATTERN = re.compile(r"^(Xe|Ge|Et|Em|Sxe|Fte|Me|Fc|Xle)-\d+/\d+/\d+[.:]*\d*$")
 
-# pylint: disable=too-few-public-methods
-
 
 # -----------------------------------------------------------------
 # Inventory Filter functions
@@ -103,7 +101,7 @@ def subtask_instance_completed(self, task: Task, host: Host, result: MultiResult
 
         if result[0].failed:
             LOGGER.warning("%s | Something went wrong while trying to pull the neighbor information", host.name)
-            host.data["status"] = "fail-other"
+            host.status = "fail-other"
             return
 
         if not isinstance(result[0].result, dict) or "neighbors" not in result[0].result:
diff --git a/network_importer/processors/get_vlans.py b/network_importer/processors/get_vlans.py
index 0d4bef24..a22f7c19 100644
--- a/network_importer/processors/get_vlans.py
+++ b/network_importer/processors/get_vlans.py
@@ -21,8 +21,6 @@
 
 LOGGER = logging.getLogger("network-importer")
 
-# pylint: disable=too-few-public-methods
-
 
 # ------------------------------------------------------------
 # Standard model to return for get_vlans
diff --git a/network_importer/tasks.py b/network_importer/tasks.py
index 4ed0bb1d..d10477b4 100644
--- a/network_importer/tasks.py
+++ b/network_importer/tasks.py
@@ -15,10 +15,11 @@
 
 import logging
 import os
-import yaml
+import socket
+from typing import Optional, List
 
+import yaml
 from nornir.core.task import Result, Task
-from nornir.plugins.tasks.networking import tcp_ping
 
 import network_importer.config as config
 
@@ -72,10 +73,56 @@ def device_save_hostvars(task: Task) -> Result:
     # # hostvars_str = template.render(dev_facts)
 
 
+def tcp_ping(task: Task, ports: List[int], timeout: int = 2, host: Optional[str] = None) -> Result:
+    """Tests connection to a tcp port and tries to establish a three way handshake.
+
+    Arguments:
+        ports (list of int): tcp ports to ping
+        timeout (int, optional): defaults to 2
+        host (string, optional): defaults to ``hostname``
+
+    Returns:
+        Result object with the following attributes set:
+          * result (``dict``): Contains port numbers as keys with True/False as values
+
+    Code copied from https://github.com/nornir-automation/nornir/blob/v2.5.0/nornir/plugins/tasks/networking/tcp_ping.py
+    Need to open a PR to https://github.com/nornir-automation/nornir_utils
+    """
+    if isinstance(ports, int):
+        ports = [ports]
+
+    if isinstance(ports, list):
+        if not all(isinstance(port, int) for port in ports):
+            raise ValueError("Invalid value for 'ports'")
+
+    else:
+        raise ValueError("Invalid value for 'ports'")
+
+    host = host or task.host.hostname
+
+    result = {}
+    for port in ports:
+        skt = socket.socket()
+        skt.settimeout(timeout)
+        try:
+            status = skt.connect_ex((host, port))
+            if status == 0:  # pylint: disable=simplifiable-if-statement
+                connection = True
+            else:
+                connection = False
+        except (socket.gaierror, socket.timeout, socket.error):
+            connection = False
+        finally:
+            skt.close()
+        result[port] = connection
+
+    return Result(host=task.host, result=result)
+
+
 def check_if_reachable(task: Task) -> Result:
     """Check if a device is reachable by doing a TCP ping it on port 22.
 
-    Will change the status of the variable `is_reachable` in host.data based on the results
+    Will change the status of the variable `host.is_reachable` based on the results
 
     Args:
       task: Nornir Task
@@ -96,17 +143,17 @@ def check_if_reachable(task: Task) -> Result:
 
     if not is_reachable:
         LOGGER.debug("%s | device is not reachable on port %s", task.host.name, port_to_check)
-        task.host.data["is_reachable"] = False
-        task.host.data["not_reachable_reason"] = f"device not reachable on port {port_to_check}"
-        task.host.data["status"] = "fail-ip"
+        task.host.is_reachable = False
+        task.host.not_reachable_reason = f"device not reachable on port {port_to_check}"
+        task.host.status = "fail-ip"
 
     return Result(host=task.host, result=is_reachable)
 
 
 def warning_not_reachable(task: Task) -> Result:
     """Generate warning logs for each unreachable device."""
-    if task.host.data.get("is_reachable"):
+    if task.host.is_reachable:
         return
 
-    reason = task.host.data.get("not_reachable_reason", "reason not defined")
+    reason = task.host.not_reachable_reason or "reason not defined"
     LOGGER.warning("%s device is not reachable, %s", task.host.name, reason)
diff --git a/network_importer/utils.py b/network_importer/utils.py
index db543d08..b7165b09 100644
--- a/network_importer/utils.py
+++ b/network_importer/utils.py
@@ -208,7 +208,7 @@ def expand_vlans_list(vlans: str) -> list:
 
 
 def build_filter_params(filter_params, params):
-    """Update params dict() with filter args in required format for pynetbox.
+    """Update params dict() with filter args in required format for pynetbox/pynautobot.
 
     Args:
       filter_params (list): split string from cli or config
diff --git a/poetry.lock b/poetry.lock
index 52fe4232..e17d272d 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -90,17 +90,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [[package]]
 name = "attrs"
-version = "20.3.0"
+version = "21.2.0"
 description = "Classes Without Boilerplate"
 category = "main"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [package.extras]
-dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"]
-docs = ["furo", "sphinx", "zope.interface"]
-tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
-tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
+docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
 
 [[package]]
 name = "bandit"
@@ -363,7 +363,7 @@ pyrepl = ">=0.8.2"
 
 [[package]]
 name = "flake8"
-version = "3.9.1"
+version = "3.9.2"
 description = "the modular source code checker: pep8 pyflakes and co"
 category = "dev"
 optional = false
@@ -453,7 +453,7 @@ dev = ["coverage", "restview", "sphinx", "sphinx-rtd-theme"]
 
 [[package]]
 name = "genie.libs.health"
-version = "21.4.1"
+version = "21.4.2"
 description = "pyATS Health Check for monitoring device health status"
 category = "main"
 optional = false
@@ -492,7 +492,7 @@ dev = ["coverage", "restview", "sphinx", "sphinx-rtd-theme"]
 
 [[package]]
 name = "genie.libs.sdk"
-version = "21.4"
+version = "21.4.1"
 description = "Genie libs sdk: Libraries containing all Triggers and Verifications"
 category = "main"
 optional = false
@@ -649,16 +649,16 @@ six = "*"
 
 [[package]]
 name = "junos-eznc"
-version = "2.5.4"
+version = "2.6.0"
 description = "Junos 'EZ' automation for non-programmers"
 category = "main"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [package.dependencies]
 jinja2 = ">=2.7.1"
 lxml = ">=3.2.4"
-ncclient = ">=0.6.3"
+ncclient = "0.6.9"
 netaddr = "*"
 paramiko = ">=1.15.2"
 pyparsing = "*"
@@ -733,7 +733,7 @@ python-versions = "*"
 
 [[package]]
 name = "napalm"
-version = "3.2.0"
+version = "3.3.0"
 description = "Network Automation and Programmability Abstraction Layer with Multivendor support"
 category = "main"
 optional = false
@@ -746,6 +746,7 @@ future = "*"
 jinja2 = "*"
 junos-eznc = ">=2.2.1"
 lxml = ">=4.3.0"
+ncclient = "*"
 netaddr = "*"
 netmiko = ">=3.1.0"
 paramiko = ">=2.6.0"
@@ -757,7 +758,7 @@ textfsm = "*"
 
 [[package]]
 name = "ncclient"
-version = "0.6.10"
+version = "0.6.9"
 description = "Python library for NETCONF clients"
 category = "main"
 optional = false
@@ -819,27 +820,55 @@ test = ["pyyaml (>=5.1.2)", "pytest (>=5.1.2)"]
 
 [[package]]
 name = "nornir"
-version = "2.5.0"
+version = "3.1.1"
 description = "Pluggable multi-threaded framework with inventory management to help operate collections of devices"
 category = "main"
 optional = false
 python-versions = ">=3.6,<4.0"
 
 [package.dependencies]
-colorama = ">=0.4.1,<0.5.0"
 dataclasses = {version = ">=0.7,<0.8", markers = "python_version >= \"3.6\" and python_version < \"3.7\""}
-jinja2 = ">=2,<3"
 mypy_extensions = ">=0.4.1,<0.5.0"
-napalm = ">=2.5.0"
-ncclient = ">=0.6.6,<0.7.0"
-netmiko = ">=2.4.2"
-paramiko = ">=2.7,<3.0"
-requests = ">=2,<3"
 "ruamel.yaml" = ">=0.16,<0.17"
 typing_extensions = ">=3.7,<4.0"
 
 [package.extras]
-docs = ["sphinx (>=1,<2)", "sphinx_rtd_theme (>=0.4,<0.5)", "sphinxcontrib-napoleon (>=0.7,<0.8)", "jupyter (>=1,<2)", "nbsphinx (>=0.5,<0.6)", "pygments (>=2,<3)", "sphinx-issues (>=1.2,<2.0)"]
+docs = ["jupyter (>=1,<2)", "nbsphinx (>=0.5,<0.6)", "pygments (>=2,<3)", "sphinx (>=1,<2)", "sphinx-issues (>=1.2,<2.0)", "sphinx_rtd_theme (>=0.4,<0.5)", "sphinxcontrib-napoleon (>=0.7,<0.8)"]
+
+[[package]]
+name = "nornir-napalm"
+version = "0.1.2"
+description = "NAPALM's plugins for nornir"
+category = "main"
+optional = false
+python-versions = ">=3.6,<4.0"
+
+[package.dependencies]
+napalm = ">=3,<4"
+nornir = ">=3,<4"
+
+[[package]]
+name = "nornir-netmiko"
+version = "0.1.1"
+description = "Netmiko's plugins for nornir"
+category = "main"
+optional = false
+python-versions = ">=3.6,<4.0"
+
+[package.dependencies]
+netmiko = ">=3.1.0,<4.0.0"
+
+[[package]]
+name = "nornir-utils"
+version = "0.1.2"
+description = "Collection of plugins and functions for nornir that don't require external dependencies"
+category = "main"
+optional = false
+python-versions = ">=3.6,<4.0"
+
+[package.dependencies]
+colorama = ">=0.4.3,<0.5.0"
+nornir = ">=3,<4"
 
 [[package]]
 name = "ntc-templates"
@@ -1308,7 +1337,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [[package]]
 name = "pydantic"
-version = "1.8.1"
+version = "1.8.2"
 description = "Data validation and settings management using python 3.6 type hinting"
 category = "main"
 optional = false
@@ -1369,7 +1398,7 @@ ssl = ["pyopenssl"]
 
 [[package]]
 name = "pygments"
-version = "2.8.1"
+version = "2.9.0"
 description = "Pygments is a syntax highlighting package written in Python."
 category = "main"
 optional = false
@@ -1414,6 +1443,20 @@ six = "*"
 docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"]
 tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"]
 
+[[package]]
+name = "pynautobot"
+version = "1.0.2"
+description = "Nautobot API client library"
+category = "main"
+optional = false
+python-versions = ">=3.6,<4.0"
+
+[package.dependencies]
+requests = ">=2.20.0,<3.0.0"
+
+[package.extras]
+docs = ["Sphinx (>=3.5.1,<4.0.0)"]
+
 [[package]]
 name = "pynetbox"
 version = "5.3.1"
@@ -1570,7 +1613,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
 
 [[package]]
 name = "requests-mock"
-version = "1.9.1"
+version = "1.9.2"
 description = "Mock out responses from the requests package"
 category = "dev"
 optional = false
@@ -1657,7 +1700,7 @@ python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*"
 
 [[package]]
 name = "six"
-version = "1.15.0"
+version = "1.16.0"
 description = "Python 2 and 3 compatibility utilities"
 category = "main"
 optional = false
@@ -1795,7 +1838,7 @@ python-versions = "*"
 
 [[package]]
 name = "typing-extensions"
-version = "3.7.4.3"
+version = "3.10.0.0"
 description = "Backported and Experimental Type Hints for Python 3.5+"
 category = "main"
 optional = false
@@ -1930,7 +1973,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.6.1"
-content-hash = "08f784c4c731ec6561281dae2d7b641d2968dc4f7e8000a51c25429b0fd15fa8"
+content-hash = "0ba99b47d2ac0769f3d2e6e7549019b23f4d6c4da05e359beccc47ffe4ebb6a4"
 
 [metadata.files]
 aiofiles = [
@@ -1995,8 +2038,8 @@ atomicwrites = [
     {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
 ]
 attrs = [
-    {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"},
-    {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"},
+    {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
+    {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
 ]
 bandit = [
     {file = "bandit-1.7.0-py3-none-any.whl", hash = "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07"},
@@ -2134,8 +2177,8 @@ fancycompleter = [
     {file = "fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272"},
 ]
 flake8 = [
-    {file = "flake8-3.9.1-py2.py3-none-any.whl", hash = "sha256:3b9f848952dddccf635be78098ca75010f073bfe14d2c6bda867154bea728d2a"},
-    {file = "flake8-3.9.1.tar.gz", hash = "sha256:1aa8990be1e689d96c745c5682b687ea49f2e05a443aff1f8251092b0014e378"},
+    {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
+    {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
 ]
 future = [
     {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
@@ -2164,7 +2207,7 @@ genie = [
     {file = "genie.libs.filetransferutils-21.4-py3-none-any.whl", hash = "sha256:523e8ad3d1c4b473d376eef5e0bbd3317d46a35fda2ea8af42a9891d014ef7cb"},
 ]
 "genie.libs.health" = [
-    {file = "genie.libs.health-21.4.1-py3-none-any.whl", hash = "sha256:2ff27107af7aabd771862e2f088796a4ca05f3e16e201a429dceb83f07b0b9a3"},
+    {file = "genie.libs.health-21.4.2-py3-none-any.whl", hash = "sha256:937e87007f6d6b7abb78fba352ea371eb416313d8668008a169d9c7762e96711"},
 ]
 "genie.libs.ops" = [
     {file = "genie.libs.ops-21.4-py3-none-any.whl", hash = "sha256:73d1decaa64355cea6a76117eb04764d8c021dd9da7c27af9918c09987520f90"},
@@ -2173,7 +2216,7 @@ genie = [
     {file = "genie.libs.parser-21.4-py3-none-any.whl", hash = "sha256:b613f6142403dc6f281695c29a7db13383d503a81ed8885e27b3b859c7199239"},
 ]
 "genie.libs.sdk" = [
-    {file = "genie.libs.sdk-21.4-py3-none-any.whl", hash = "sha256:3697d8985c84933d92a0d9aeb0f1b1a13b138f0b43a24d05f4fa1b07ee94df74"},
+    {file = "genie.libs.sdk-21.4.1-py3-none-any.whl", hash = "sha256:192815052165c57fdcfd9e1c9a7406b572cfd26df352e177a5de7d227bf8428c"},
 ]
 gitdb = [
     {file = "gitdb-4.0.7-py3-none-any.whl", hash = "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0"},
@@ -2223,8 +2266,8 @@ junit-xml = [
     {file = "junit_xml-1.9-py2.py3-none-any.whl", hash = "sha256:ec5ca1a55aefdd76d28fcc0b135251d156c7106fa979686a4b48d62b761b4732"},
 ]
 junos-eznc = [
-    {file = "junos-eznc-2.5.4.tar.gz", hash = "sha256:bf036d0af9ee5c5e4f517cb5fc902fe891fa120e18f459805862c53d4a97193a"},
-    {file = "junos_eznc-2.5.4-py2.py3-none-any.whl", hash = "sha256:e05c36d56d8b8d13b1fb3bb763828bb3ee80fa1dcadc3a6762e8e2568504676d"},
+    {file = "junos-eznc-2.6.0.tar.gz", hash = "sha256:761a85a1485f8dc41b983ae7296889c7a78394b78d2de68f0831b7efa5f6ca34"},
+    {file = "junos_eznc-2.6.0-py2.py3-none-any.whl", hash = "sha256:598c3569b21f362f0bad626ad3756f9f8a3f4d507a0ff37ff76e33b3ed73859e"},
 ]
 lazy-object-proxy = [
     {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"},
@@ -2375,11 +2418,11 @@ mypy-extensions = [
     {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
 ]
 napalm = [
-    {file = "napalm-3.2.0-py2.py3-none-any.whl", hash = "sha256:6ab07a8a360150198b3c33c2fa4e92a9658cda9821736d24f2399343425d2af3"},
-    {file = "napalm-3.2.0.tar.gz", hash = "sha256:6e69a505f5d4a678c42f048d13153afabf10b7fec72868e3bfb53ff4089206fc"},
+    {file = "napalm-3.3.0-py2.py3-none-any.whl", hash = "sha256:7708036fd17a38711643b8911c942b4bb3b59556e2806d31c4392605e2b7aa35"},
+    {file = "napalm-3.3.0.tar.gz", hash = "sha256:0ac7901105fed5f67b1b6e498b0b2b008f77b72c9c1ef323059a97125753138b"},
 ]
 ncclient = [
-    {file = "ncclient-0.6.10.tar.gz", hash = "sha256:67b1eba5a6c7c6075746d8c33d4e8f4ded17604034c1fcd1c78996ef52bf66ff"},
+    {file = "ncclient-0.6.9.tar.gz", hash = "sha256:0112f2ad41fb658f52446d870853a63691d69299c73c7351c520d38dbd8dc0c4"},
 ]
 netaddr = [
     {file = "netaddr-0.8.0-py2.py3-none-any.whl", hash = "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac"},
@@ -2394,8 +2437,20 @@ netmiko = [
     {file = "netmiko-3.4.0.tar.gz", hash = "sha256:acadb9dd97864ee848e2032f1f0e301c7b31e7a4153757d98f5c8ba1b9614993"},
 ]
 nornir = [
-    {file = "nornir-2.5.0-py3-none-any.whl", hash = "sha256:c67ea392869df7538b81094c44c23d4f330a65920916e1b799e12555abb51fc8"},
-    {file = "nornir-2.5.0.tar.gz", hash = "sha256:5f82bece03b15ba00b46e87a7960cd19d96cb18e02a5b7db1127b20b528e2609"},
+    {file = "nornir-3.1.1-py3-none-any.whl", hash = "sha256:217199f923c810f4a54dec8d440eb08682c8a4ea4746325bd3067dca2e32cf9f"},
+    {file = "nornir-3.1.1.tar.gz", hash = "sha256:6abf8ca245aab8e36b5770ec94fdb456b6bf53696c6d68f2ee0882898630a392"},
+]
+nornir-napalm = [
+    {file = "nornir_napalm-0.1.2-py3-none-any.whl", hash = "sha256:313986bbb16eeaf3a80daf61f6a97e93ecebc47d0c586a2eb856ea91edc2cc1d"},
+    {file = "nornir_napalm-0.1.2.tar.gz", hash = "sha256:be7808a990242987500a65701edb626197c5d0b87f35d9eb5da7ce7e4d60fdd5"},
+]
+nornir-netmiko = [
+    {file = "nornir_netmiko-0.1.1-py3-none-any.whl", hash = "sha256:c6eadb81f6f3b2f0c27bae151cc62673303f9d085ec3c773ecdc98f20ef30f91"},
+    {file = "nornir_netmiko-0.1.1.tar.gz", hash = "sha256:fc41ded40923d23c6155b92c6749170629b4cc2649c340d24bff9f49315836c6"},
+]
+nornir-utils = [
+    {file = "nornir_utils-0.1.2-py3-none-any.whl", hash = "sha256:98915e781d4135e424fe1d4617dc231ada9a7c732c8e51e408f53fd3d37a13b0"},
+    {file = "nornir_utils-0.1.2.tar.gz", hash = "sha256:23ae95c4805b0ce8a5ed32935f3f86027e5701175e7740ab8b3a79946c5d90b2"},
 ]
 ntc-templates = [
     {file = "ntc_templates-1.7.0-py3-none-any.whl", hash = "sha256:08ffeafd02a1d3e42f4f12f910f0b4c64b93783d76ece186c9a63389403a450b"},
@@ -2739,28 +2794,28 @@ pycparser = [
     {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
 ]
 pydantic = [
-    {file = "pydantic-1.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0c40162796fc8d0aa744875b60e4dc36834db9f2a25dbf9ba9664b1915a23850"},
-    {file = "pydantic-1.8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fff29fe54ec419338c522b908154a2efabeee4f483e48990f87e189661f31ce3"},
-    {file = "pydantic-1.8.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:fbfb608febde1afd4743c6822c19060a8dbdd3eb30f98e36061ba4973308059e"},
-    {file = "pydantic-1.8.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:eb8ccf12295113ce0de38f80b25f736d62f0a8d87c6b88aca645f168f9c78771"},
-    {file = "pydantic-1.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:20d42f1be7c7acc352b3d09b0cf505a9fab9deb93125061b376fbe1f06a5459f"},
-    {file = "pydantic-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dde4ca368e82791de97c2ec019681ffb437728090c0ff0c3852708cf923e0c7d"},
-    {file = "pydantic-1.8.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3bbd023c981cbe26e6e21c8d2ce78485f85c2e77f7bab5ec15b7d2a1f491918f"},
-    {file = "pydantic-1.8.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:830ef1a148012b640186bf4d9789a206c56071ff38f2460a32ae67ca21880eb8"},
-    {file = "pydantic-1.8.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:fb77f7a7e111db1832ae3f8f44203691e15b1fa7e5a1cb9691d4e2659aee41c4"},
-    {file = "pydantic-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:3bcb9d7e1f9849a6bdbd027aabb3a06414abd6068cb3b21c49427956cce5038a"},
-    {file = "pydantic-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2287ebff0018eec3cc69b1d09d4b7cebf277726fa1bd96b45806283c1d808683"},
-    {file = "pydantic-1.8.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:4bbc47cf7925c86a345d03b07086696ed916c7663cb76aa409edaa54546e53e2"},
-    {file = "pydantic-1.8.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:6388ef4ef1435364c8cc9a8192238aed030595e873d8462447ccef2e17387125"},
-    {file = "pydantic-1.8.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:dd4888b300769ecec194ca8f2699415f5f7760365ddbe243d4fd6581485fa5f0"},
-    {file = "pydantic-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:8fbb677e4e89c8ab3d450df7b1d9caed23f254072e8597c33279460eeae59b99"},
-    {file = "pydantic-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2f2736d9a996b976cfdfe52455ad27462308c9d3d0ae21a2aa8b4cd1a78f47b9"},
-    {file = "pydantic-1.8.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:3114d74329873af0a0e8004627f5389f3bb27f956b965ddd3e355fe984a1789c"},
-    {file = "pydantic-1.8.1-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:258576f2d997ee4573469633592e8b99aa13bda182fcc28e875f866016c8e07e"},
-    {file = "pydantic-1.8.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:c17a0b35c854049e67c68b48d55e026c84f35593c66d69b278b8b49e2484346f"},
-    {file = "pydantic-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:e8bc082afef97c5fd3903d05c6f7bb3a6af9fc18631b4cc9fedeb4720efb0c58"},
-    {file = "pydantic-1.8.1-py3-none-any.whl", hash = "sha256:e3f8790c47ac42549dc8b045a67b0ca371c7f66e73040d0197ce6172b385e520"},
-    {file = "pydantic-1.8.1.tar.gz", hash = "sha256:26cf3cb2e68ec6c0cfcb6293e69fb3450c5fd1ace87f46b64f678b0d29eac4c3"},
+    {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"},
+    {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"},
+    {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"},
+    {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"},
+    {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"},
+    {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"},
+    {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"},
+    {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"},
+    {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"},
+    {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"},
+    {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"},
+    {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"},
+    {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"},
+    {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"},
+    {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"},
+    {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"},
+    {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"},
+    {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"},
+    {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"},
+    {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"},
+    {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"},
+    {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"},
 ]
 pydocstyle = [
     {file = "pydocstyle-5.1.1-py3-none-any.whl", hash = "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"},
@@ -2777,8 +2832,8 @@ pyftpdlib = [
     {file = "pyftpdlib-1.5.6.tar.gz", hash = "sha256:fda655d81f29af52885ca2f8a2704134baed540f16d66a0b26e8fdfafd12db5e"},
 ]
 pygments = [
-    {file = "Pygments-2.8.1-py3-none-any.whl", hash = "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"},
-    {file = "Pygments-2.8.1.tar.gz", hash = "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94"},
+    {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"},
+    {file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"},
 ]
 pylint = [
     {file = "pylint-2.8.2-py3-none-any.whl", hash = "sha256:f7e2072654a6b6afdf5e2fb38147d3e2d2d43c89f648637baab63e026481279b"},
@@ -2806,6 +2861,10 @@ pynacl = [
     {file = "PyNaCl-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420"},
     {file = "PyNaCl-1.4.0.tar.gz", hash = "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505"},
 ]
+pynautobot = [
+    {file = "pynautobot-1.0.2-py3-none-any.whl", hash = "sha256:19dd6fbb4663440f0d890137ff58dd5206cb4452f885f3eba7874888ca22727d"},
+    {file = "pynautobot-1.0.2.tar.gz", hash = "sha256:8cb0afe97a48f16301a7a4bd58e409b4517fb5c9c9fc808f2cce6ea4e99e1408"},
+]
 pynetbox = [
     {file = "pynetbox-5.3.1-py3-none-any.whl", hash = "sha256:b5326f3483b873ba1cb230e36ef5a7cd732b6f5428a0005b2978f3b2c40105db"},
     {file = "pynetbox-5.3.1.tar.gz", hash = "sha256:80743328f51f28827825fb2c9cb1993175da37c88988f02075f6f50e16333e0c"},
@@ -2917,8 +2976,8 @@ requests = [
     {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
 ]
 requests-mock = [
-    {file = "requests-mock-1.9.1.tar.gz", hash = "sha256:154903e7646b8215fbdccc6898c32a77b87d2fbafad2f43e99d6d7987dc86953"},
-    {file = "requests_mock-1.9.1-py2.py3-none-any.whl", hash = "sha256:705c03efff5c4e38a00eb203e71e9b4381fbce9679870bda3ac533b61a0b8b9d"},
+    {file = "requests-mock-1.9.2.tar.gz", hash = "sha256:33296f228d8c5df11a7988b741325422480baddfdf5dd9318fd0eb40c3ed8595"},
+    {file = "requests_mock-1.9.2-py2.py3-none-any.whl", hash = "sha256:5c8ef0254c14a84744be146e9799dc13ebc4f6186058112d9aeed96b131b58e2"},
 ]
 requests-toolbelt = [
     {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"},
@@ -2940,20 +2999,29 @@ rich = [
     {file = "ruamel.yaml.clib-0.2.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:73b3d43e04cc4b228fa6fa5d796409ece6fcb53a6c270eb2048109cbcbc3b9c2"},
     {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:53b9dd1abd70e257a6e32f934ebc482dac5edb8c93e23deb663eac724c30b026"},
     {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:839dd72545ef7ba78fd2aa1a5dd07b33696adf3e68fae7f31327161c1093001b"},
+    {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1236df55e0f73cd138c0eca074ee086136c3f16a97c2ac719032c050f7e0622f"},
     {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-win32.whl", hash = "sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f"},
     {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4e52c96ca66de04be42ea2278012a2342d89f5e82b4512fb6fb7134e377e2e62"},
     {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c"},
     {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988"},
+    {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:2fd336a5c6415c82e2deb40d08c222087febe0aebe520f4d21910629018ab0f3"},
     {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-win32.whl", hash = "sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2"},
     {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:2602e91bd5c1b874d6f93d3086f9830f3e907c543c7672cf293a97c3fabdcd91"},
     {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:44c7b0498c39f27795224438f1a6be6c5352f82cb887bc33d962c3a3acc00df6"},
     {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8e8fd0a22c9d92af3a34f91e8a2594eeb35cba90ab643c5e0e643567dc8be43e"},
+    {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:75f0ee6839532e52a3a53f80ce64925ed4aed697dd3fa890c4c918f3304bd4f4"},
     {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-win32.whl", hash = "sha256:464e66a04e740d754170be5e740657a3b3b6d2bcc567f0c3437879a6e6087ff6"},
     {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:52ae5739e4b5d6317b52f5b040b1b6639e8af68a5b8fd606a8b08658fbd0cab5"},
     {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df5019e7783d14b79217ad9c56edf1ba7485d614ad5a385d1b3c768635c81c0"},
     {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5254af7d8bdf4d5484c089f929cb7f5bafa59b4f01d4f48adda4be41e6d29f99"},
+    {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8be05be57dc5c7b4a0b24edcaa2f7275866d9c907725226cdde46da09367d923"},
     {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-win32.whl", hash = "sha256:74161d827407f4db9072011adcfb825b5258a5ccb3d2cd518dd6c9edea9e30f1"},
     {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:058a1cc3df2a8aecc12f983a48bda99315cebf55a3b3a5463e37bb599b05727b"},
+    {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a"},
+    {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5"},
+    {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1f8c0a4577c0e6c99d208de5c4d3fd8aceed9574bb154d7a2b21c16bb924154c"},
+    {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-win32.whl", hash = "sha256:46d6d20815064e8bb023ea8628cfb7402c0f0e83de2c2227a88097e239a7dffd"},
+    {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:6c0a5dc52fc74eb87c67374a4e554d4761fd42a4d01390b7e868b30d21f4b8bb"},
     {file = "ruamel.yaml.clib-0.2.2.tar.gz", hash = "sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7"},
 ]
 scp = [
@@ -3008,8 +3076,8 @@ simplejson = [
     {file = "simplejson-3.17.2.tar.gz", hash = "sha256:75ecc79f26d99222a084fbdd1ce5aad3ac3a8bd535cd9059528452da38b68841"},
 ]
 six = [
-    {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
-    {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
+    {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+    {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
 ]
 smmap = [
     {file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"},
@@ -3085,9 +3153,9 @@ typed-ast = [
     {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"},
 ]
 typing-extensions = [
-    {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"},
-    {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
-    {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
+    {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"},
+    {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"},
+    {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"},
 ]
 unicon = [
     {file = "unicon-21.4-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:5be62860fc3d16d1e9562aa1a0a233f19d3b71a9dd8a54d7906768816b1670fe"},
diff --git a/pyproject.toml b/pyproject.toml
index 1960560e..56e1bb00 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,7 +21,7 @@ packages = [
 python = "^3.6.1"
 pynetbox = "^5.0"
 toml = "^0.10"
-nornir = "^2.4"
+nornir = "^3.0"
 termcolor = "^1.1"
 click = "^7.1"
 pydantic = "^1.6"
@@ -32,6 +32,10 @@ ntc-templates = "^1.6.0"
 structlog = "^20.1.0"
 diffsync = "~1.2.0"
 rich = "^9.2.0"
+pynautobot = "^1.0.2"
+nornir-napalm = "^0.1.2"
+nornir-utils = "^0.1.2"
+nornir-netmiko = "^0.1.1"
 pybatfish = "2021.2.26.830"
 
 [tool.poetry.dev-dependencies]
@@ -77,7 +81,8 @@ no-docstring-rgx="^(_|test_)"
 disable = """,
     line-too-long,
     bad-continuation,
-    duplicate-code
+    duplicate-code,
+    too-few-public-methods
     """
 
 [tool.pylint.miscellaneous]
diff --git a/tasks.py b/tasks.py
index 77319375..248d1513 100644
--- a/tasks.py
+++ b/tasks.py
@@ -16,6 +16,8 @@
     "v2.8": {"netbox_version": "v2.8.9", "docker_version": "0.24.1"},
 }
 
+NAUTOBOT_VER = "v1.0.1"
+
 
 def project_ver():
     """Find version from pyproject.toml to use for docker image tagging."""
@@ -58,6 +60,10 @@ def is_truthy(arg):
 TRAVIS_ANSIBLE_PYTHON_INTERPRETER = "$(which python)"
 TRAVIS_EXAMPLES = ["spine_leaf_01", "multi_site_02"]
 TRAVIS_BATFISH_VERSION = "2020.10.08.667"
+TRAVIS_NAUTOBOT_ADDRESS = "http://localhost:8000"
+TRAVIS_NAUTOBOT_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"  # nosec - bandit ignore possible password
+TRAVIS_NAUTOBOT_VERIFY_SSL = "false"
+NAUTOBOT_VERSION = os.getenv("NAUTOBOT_VERSION", "v1.0.1")
 
 
 def run_cmd(context, exec_cmd, name=NAME, image_ver=IMAGE_VER, local=INVOKE_LOCAL):
@@ -316,18 +322,23 @@ def configure_netbox(context, example_name, var_envs):
     context.run(f"cd {PWD}/examples/{example_name} && ansible-playbook pb.netbox_setup.yaml", pty=True, env=var_envs)
 
 
-def run_network_importer(context, example_name, var_envs):
+def run_network_importer(context, example_name, var_envs, config_file="network_importer.toml"):
     """Run Network Importer.
 
     Args:
         context (obj): Used to run specific commands
         example_name (str): Name of the example directory to use
         var_envs (dict): Environment variables to pass to the command runner
+        config_file (str): Name of the configuration file. Optional, defaults to `network_importer.toml`
     """
-    context.run(f"cd {PWD}/examples/{example_name} && network-importer check", pty=True, env=var_envs)
-    context.run(f"cd {PWD}/examples/{example_name} && network-importer apply", pty=True, env=var_envs)
+    context.run(
+        f"cd {PWD}/examples/{example_name} && network-importer check  --config {config_file}", pty=True, env=var_envs
+    )
+    context.run(
+        f"cd {PWD}/examples/{example_name} && network-importer apply --config {config_file}", pty=True, env=var_envs
+    )
     output_last_check = context.run(
-        f"cd {PWD}/examples/{example_name} && network-importer check", pty=True, env=var_envs
+        f"cd {PWD}/examples/{example_name} && network-importer check --config {config_file}", pty=True, env=var_envs
     )
 
     if "no diffs" not in output_last_check.stdout:
@@ -366,6 +377,83 @@ def integration_tests(context, netbox_ver=NETBOX_VERSION):
     )
 
 
+def compose_nautobot(context):
+    """Create Netbox instance for Travis testing.
+
+    Args:
+        context (obj): Used to run specific commands
+        var_envs (dict): Environment variables to pass to the command runner
+        netbox_docker_ver (str): Version of Netbox docker to use
+    """
+    # Copy the file from tests/docker-compose.test.yml to the tmp directory to be executed from there
+    # context.run(
+    #     f"cp {PWD}/tests/nautobot-docker-compose.test.yml /tmp/docker-compose.yml", pty=True, env=var_envs,
+    # )
+    # context.run(
+    #     f"cp {PWD}/tests/nginx.conf /tmp/nginx.conf", pty=True, env=var_envs,
+    # )
+    # context.run(
+    #     f"cp {PWD}/tests/.creds.env.test /tmp/.creds.tests.env", pty=True, env=var_envs,
+    # )
+    # context.run("cd /tmp && docker-compose pull", pty=True, env=var_envs)
+    # context.run("cd /tmp && docker-compose down", pty=True, env=var_envs)
+    # context.run("cd /tmp && docker-compose up -d", pty=True, env=var_envs)
+
+    # Clone the repo so the latest data is present
+    context.run("docker pull networktocode/nautobot-lab:latest ", pty=True)
+
+    # Start the container
+    context.run("docker run -itd --rm --name nautobot -p 8000:8000 networktocode/nautobot-lab:latest", pty=True)
+
+    # Execute the load demo data
+    context.run("sleep 5 && docker exec -it nautobot load-mock-data", pty=True)
+
+    # Print out the ports listening to verify it is running
+    context.run("ss -ltn", pty=True)
+
+
+def configure_nautobot(context, example_name, var_envs):
+    """Configure Netbox instance with Ansible.
+
+    Args:
+        context (obj): Used to run specific commands
+        example_name (str): Name of the example directory to use
+        var_envs (dict): Environment variables to pass to the command runner
+    """
+    # Sleep for a minute to allow all systems to be up and running
+    context.run("sleep 60", pty=True, env=var_envs)
+    context.run(f"cd {PWD}/examples/{example_name} && python3 nautobot_setup.py", pty=True, env=var_envs)
+    # context.run(
+    #     f"cd {PWD}/examples/{example_name} && ansible-playbook pb.nautobot_setup.yaml -vv ", pty=True, env=var_envs
+    # )
+
+
+@task
+def nautobot_integration_tests(context, nautobot_ver=NAUTOBOT_VER):
+    """Builds Nautobot test environment for Travis-CI.
+
+    Args:
+        context (obj): Used to run specific commands
+        nautobot_ver (str): Nautobot version to use for testing
+    """
+    envs = {
+        "VERSION": nautobot_ver,
+        "NAUTOBOT_ADDRESS": TRAVIS_NAUTOBOT_ADDRESS,
+        "NAUTOBOT_TOKEN": TRAVIS_NAUTOBOT_TOKEN,
+        "NAUTOBOT_VERIFY_SSL": TRAVIS_NAUTOBOT_VERIFY_SSL,
+        "BATFISH_ADDRESS": TRAVIS_BATFISH_ADDRESS,
+        "ANSIBLE_PYTHON_INTERPRETER": TRAVIS_ANSIBLE_PYTHON_INTERPRETER,
+    }
+
+    compose_nautobot(context)
+    compose_batfish(context, var_envs=envs)
+    time.sleep(90)
+    for example in TRAVIS_EXAMPLES:
+        configure_nautobot(context, example, var_envs=envs)
+        run_network_importer(context, example, var_envs=envs, config_file="network_importer_nautobot.toml")
+    print(f"All integration tests have passed for Nautobot {nautobot_ver}")
+
+
 @task
 def tests(context, name=NAME, image_ver=IMAGE_VER, local=INVOKE_LOCAL):
     """This will run all tests for the specified name and Python version.


Test patch:
diff --git a/tests/.creds.env.test b/tests/.creds.env.test
new file mode 100644
index 00000000..3fdf2ea8
--- /dev/null
+++ b/tests/.creds.env.test
@@ -0,0 +1,28 @@
+NAUTOBOT_ALLOWED_HOSTS=*
+NAUTOBOT_CHANGELOG_RETENTION=0
+NAUTOBOT_CONFIG=/opt/nautobot/nautobot_config.py
+NAUTOBOT_DB_HOST=postgres
+NAUTOBOT_DB_NAME=nautobot
+NAUTOBOT_DB_PASSWORD=insecure_password
+NAUTOBOT_DB_USER=nautobot
+NAUTOBOT_MAX_PAGE_SIZE=0
+NAUTOBOT_NAPALM_TIMEOUT=5
+NAUTOBOT_REDIS_HOST=redis-queue
+NAUTOBOT_REDIS_PASSWORD=insecure_password
+NAUTOBOT_CACHEOPS_REDIS=redis://:insecure_password@redis-cacheops:6379/0
+NAUTOBOT_REDIS_PORT=6379
+# Uncomment REDIS_SSL if using SSL
+# NAUTOBOT_REDIS_SSL=True
+NAUTOBOT_SECRET_KEY=012345678901234567890123456789012345678901234567890123456789
+# Needed for Postgres should match the values for Nautobot above
+PGPASSWORD=insecure_password
+POSTGRES_DB=nautobot
+POSTGRES_PASSWORD=insecure_password
+POSTGRES_USER=nautobot
+# Needed for Redis should match the values for Nautobot above
+NAUTOBOT_HIDE_RESTRICTED_UI=True
+NAUTOBOT_CREATE_SUPERUSER=true
+NAUTOBOT_SUPERUSER_NAME=admin
+NAUTOBOT_SUPERUSER_EMAIL=admin@example.com
+NAUTOBOT_SUPERUSER_PASSWORD=admin
+NAUTOBOT_SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
\ No newline at end of file
diff --git a/tests/ansible.cfg b/tests/ansible.cfg
new file mode 100644
index 00000000..73bfc1c7
--- /dev/null
+++ b/tests/ansible.cfg
@@ -0,0 +1,2 @@
+[defaults]
+forks=1
\ No newline at end of file
diff --git a/tests/nautobot-docker-compose.test.yml b/tests/nautobot-docker-compose.test.yml
new file mode 100644
index 00000000..ed59e193
--- /dev/null
+++ b/tests/nautobot-docker-compose.test.yml
@@ -0,0 +1,47 @@
+---
+version: "3.7"
+services:
+  nginx:
+    image: "nginx:1.19"
+    ports:
+      - "8000:8080"  # Configured in F5 Load balancer to foward to port 8080 over HTTPS
+    volumes:
+      - "./nginx.conf:/etc/nginx/nginx.conf"
+  nautobot:
+    image: "networktocode/nautobot:${NAUTOBOT_VERSION}"
+    ports:
+      - "8443:8443"
+      - "8080:8080"
+    env_file:
+      - "/tmp/.creds.tests.env"
+  nautobot-worker:
+    image: "networktocode/nautobot:${NAUTOBOT_VERSION}"
+    entrypoint: "nautobot-server rqworker"
+    depends_on:
+      - "nautobot"
+    env_file:
+      - "/tmp/.creds.tests.env"
+  redis-cacheops:
+    image: "redis:alpine"
+    command:
+      - "sh"
+      - "-c"  # this is to evaluate the $REDIS_PASSWORD from the env
+      - "redis-server --appendonly yes --requirepass $$NAUTOBOT_REDIS_PASSWORD"  ## $$ because of docker-compose
+    env_file:
+      - "/tmp/.creds.tests.env"
+  redis-queue:
+    image: "redis:alpine"
+    command:
+      - "sh"
+      - "-c"  # this is to evaluate the $REDIS_PASSWORD from the env
+      - "redis-server --appendonly yes --requirepass $$NAUTOBOT_REDIS_PASSWORD"  ## $$ because of docker-compose
+    env_file:
+      - "/tmp/.creds.tests.env"
+  postgres:
+    image: "postgres:10"
+    volumes:
+      - "postgres_data:/var/lib/postgresql/data"
+    env_file:
+      - "/tmp/.creds.tests.env"
+volumes:
+  postgres_data:
diff --git a/tests/nginx.conf b/tests/nginx.conf
new file mode 100644
index 00000000..f9e55dec
--- /dev/null
+++ b/tests/nginx.conf
@@ -0,0 +1,19 @@
+events {
+    worker_connections 1024;
+}
+
+http {
+    resolver 127.0.0.11;
+
+    server {
+        listen              8080;
+        access_log  off;
+        location / {
+            proxy_pass http://nautobot:8080;
+            proxy_set_header X-Forwarded-Host $http_host;
+            proxy_set_header X-Real-IP $remote_addr;
+            proxy_set_header X-Forwarded-Proto $scheme;
+            add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/unit/adapters/nautobot_api/fixtures/inventory/devices.json b/tests/unit/adapters/nautobot_api/fixtures/inventory/devices.json
new file mode 100644
index 00000000..9510e708
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/fixtures/inventory/devices.json
@@ -0,0 +1,219 @@
+{
+    "count": 3,
+    "next": null,
+    "previous": null,
+    "results": [
+        {
+            "id": "121cb69d-bbff-4867-81e4-cbb2ba1bd93f",
+            "url": "https://localhost/api/dcim/devices/121cb69d-bbff-4867-81e4-cbb2ba1bd93f/",
+            "name": "grb-rtr01",
+            "device_type": {
+                "id": "599a9bd7-0a32-484d-b815-d9557bcc86e6",
+                "url": "https://localhost/api/dcim/device-types/599a9bd7-0a32-484d-b815-d9557bcc86e6/",
+                "manufacturer": {
+                    "id": "819ff875-95b2-4595-b09a-821420894312",
+                    "url": "https://localhost/api/dcim/manufacturers/819ff875-95b2-4595-b09a-821420894312/",
+                    "name": "Cisco",
+                    "slug": "cisco",
+                    "display": "Cisco"
+                },
+                "model": "IOSv",
+                "slug": "iosv",
+                "display": "Cisco IOSv"
+            },
+            "device_role": {
+                "id": "a4027f8b-60a2-4c73-9de2-1d64c4ff4cb6",
+                "url": "https://localhost/api/dcim/device-roles/a4027f8b-60a2-4c73-9de2-1d64c4ff4cb6/",
+                "name": "Router",
+                "slug": "router",
+                "display": "Router"
+            },
+            "tenant": null,
+            "platform": {
+                "id": "98525687-3f7c-4413-933d-1b5602f1fa7d",
+                "url": "https://localhost/api/dcim/platforms/98525687-3f7c-4413-933d-1b5602f1fa7d/",
+                "name": "Cisco IOS",
+                "slug": "ios",
+                "display": "Cisco IOS"
+            },
+            "serial": "",
+            "asset_tag": null,
+            "site": {
+                "id": "ba9d7504-e17b-4b7a-b582-bc3800ebe680",
+                "url": "https://localhost/api/dcim/sites/ba9d7504-e17b-4b7a-b582-bc3800ebe680/",
+                "name": "GRB",
+                "slug": "grb",
+                "display": "GRB"
+            },
+            "rack": null,
+            "position": null,
+            "face": null,
+            "parent_device": null,
+            "status": {
+                "value": "active",
+                "label": "Active"
+            },
+            "primary_ip": null,
+            "primary_ip4": null,
+            "primary_ip6": null,
+            "cluster": null,
+            "virtual_chassis": null,
+            "vc_position": null,
+            "vc_priority": null,
+            "comments": "",
+            "local_context_data": null,
+            "tags": [],
+            "custom_fields": {},
+            "config_context": {},
+            "created": "2021-05-04",
+            "last_updated": "2021-05-04T20:42:32.056461Z",
+            "display": "grb-rtr01"
+        },
+        {
+            "id": "07b4d653-c8fb-4473-8727-11eed93bfcbf",
+            "url": "https://localhost/api/dcim/devices/07b4d653-c8fb-4473-8727-11eed93bfcbf/",
+            "name": "msp-rtr01",
+            "device_type": {
+                "id": "fb72b94e-2872-4c50-aef4-fcb54d59d185",
+                "url": "https://localhost/api/dcim/device-types/fb72b94e-2872-4c50-aef4-fcb54d59d185/",
+                "manufacturer": {
+                    "id": "819ff875-95b2-4595-b09a-821420894312",
+                    "url": "https://localhost/api/dcim/manufacturers/819ff875-95b2-4595-b09a-821420894312/",
+                    "name": "Cisco",
+                    "slug": "cisco",
+                    "display": "Cisco"
+                },
+                "model": "nxosv",
+                "slug": "nxosv",
+                "display": "Cisco nxosv"
+            },
+            "device_role": {
+                "id": "a4027f8b-60a2-4c73-9de2-1d64c4ff4cb6",
+                "url": "https://localhost/api/dcim/device-roles/a4027f8b-60a2-4c73-9de2-1d64c4ff4cb6/",
+                "name": "Router",
+                "slug": "router",
+                "display": "Router"
+            },
+            "tenant": null,
+            "platform": {
+                "id": "4b68c8b7-9119-4fd1-8aaf-484e0ea98529",
+                "url": "https://localhost/api/dcim/platforms/4b68c8b7-9119-4fd1-8aaf-484e0ea98529/",
+                "name": "Cisco NXOS",
+                "slug": "nxos",
+                "display": "Cisco NXOS"
+            },
+            "serial": "",
+            "asset_tag": null,
+            "site": {
+                "id": "e13825ac-6996-44ab-9a41-2f35a8b04466",
+                "url": "https://localhost/api/dcim/sites/e13825ac-6996-44ab-9a41-2f35a8b04466/",
+                "name": "MSP",
+                "slug": "msp",
+                "display": "MSP"
+            },
+            "rack": null,
+            "position": null,
+            "face": null,
+            "parent_device": null,
+            "status": {
+                "value": "active",
+                "label": "Active"
+            },
+            "primary_ip": null,
+            "primary_ip4": null,
+            "primary_ip6": null,
+            "cluster": null,
+            "virtual_chassis": null,
+            "vc_position": null,
+            "vc_priority": null,
+            "comments": "",
+            "local_context_data": null,
+            "tags": [],
+            "custom_fields": {},
+            "config_context": {},
+            "created": "2021-05-04",
+            "last_updated": "2021-05-11T01:13:34.799672Z",
+            "display": "msp-rtr01"
+        },
+        {
+            "id": "794d8dc0-ce43-4857-a70f-3020e8aa861b",
+            "url": "https://localhost/api/dcim/devices/794d8dc0-ce43-4857-a70f-3020e8aa861b/",
+            "name": "sw01",
+            "device_type": {
+                "id": "d6f5a5a9-93b8-4626-813d-247051887fb6",
+                "url": "https://localhost/api/dcim/device-types/d6f5a5a9-93b8-4626-813d-247051887fb6/",
+                "manufacturer": {
+                    "id": "819ff875-95b2-4595-b09a-821420894312",
+                    "url": "https://localhost/api/dcim/manufacturers/819ff875-95b2-4595-b09a-821420894312/",
+                    "name": "Cisco",
+                    "slug": "cisco",
+                    "display": "Cisco"
+                },
+                "model": "WS-C3750G-48PS",
+                "slug": "ws-c3750g-48ps",
+                "display": "Cisco WS-C3750G-48PS"
+            },
+            "device_role": {
+                "id": "d520822f-7841-480b-bb95-c449d26ca5d0",
+                "url": "https://localhost/api/dcim/device-roles/d520822f-7841-480b-bb95-c449d26ca5d0/",
+                "name": "network",
+                "slug": "network",
+                "display": "network"
+            },
+            "tenant": null,
+            "platform": {
+                "id": "24763d85-f0bc-46f0-a7ab-22b404c3d715",
+                "url": "https://localhost/api/dcim/platforms/24763d85-f0bc-46f0-a7ab-22b404c3d715/",
+                "name": "cisco_ios",
+                "slug": "cisco_ios",
+                "display": "cisco_ios"
+            },
+            "serial": "FOC0948Y2RB",
+            "asset_tag": null,
+            "site": {
+                "id": "e13825ac-6996-44ab-9a41-2f35a8b04466",
+                "url": "https://localhost/api/dcim/sites/e13825ac-6996-44ab-9a41-2f35a8b04466/",
+                "name": "MSP",
+                "slug": "msp",
+                "display": "MSP"
+            },
+            "rack": null,
+            "position": null,
+            "face": null,
+            "parent_device": null,
+            "status": {
+                "value": "active",
+                "label": "Active"
+            },
+            "primary_ip": {
+                "id": "4e829ee7-abae-435a-bd3c-d6a87a135269",
+                "url": "https://localhost/api/ipam/ip-addresses/4e829ee7-abae-435a-bd3c-d6a87a135269/",
+                "family": 4,
+                "address": "10.250.0.2/24",
+                "display": "10.250.0.2/24"
+            },
+            "primary_ip4": {
+                "id": "4e829ee7-abae-435a-bd3c-d6a87a135269",
+                "url": "https://localhost/api/ipam/ip-addresses/4e829ee7-abae-435a-bd3c-d6a87a135269/",
+                "family": 4,
+                "address": "10.250.0.2/24",
+                "display": "10.250.0.2/24"
+            },
+            "primary_ip6": null,
+            "cluster": null,
+            "virtual_chassis": null,
+            "vc_position": null,
+            "vc_priority": null,
+            "comments": "",
+            "local_context_data": null,
+            "tags": [],
+            "custom_fields": {},
+            "config_context": {
+                "ansible_network_os": "ios"
+            },
+            "created": "2021-04-29",
+            "last_updated": "2021-04-29T18:47:33.170873Z",
+            "display": "sw01"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/tests/unit/adapters/nautobot_api/fixtures/inventory/filtered_devices.json b/tests/unit/adapters/nautobot_api/fixtures/inventory/filtered_devices.json
new file mode 100644
index 00000000..4f334b08
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/fixtures/inventory/filtered_devices.json
@@ -0,0 +1,73 @@
+{
+    "count": 1,
+    "next": null,
+    "previous": null,
+    "results": [
+        {
+            "id": "121cb69d-bbff-4867-81e4-cbb2ba1bd93f",
+            "url": "https://localhost/api/dcim/devices/121cb69d-bbff-4867-81e4-cbb2ba1bd93f/",
+            "name": "grb-rtr01",
+            "device_type": {
+                "id": "599a9bd7-0a32-484d-b815-d9557bcc86e6",
+                "url": "https://localhost/api/dcim/device-types/599a9bd7-0a32-484d-b815-d9557bcc86e6/",
+                "manufacturer": {
+                    "id": "819ff875-95b2-4595-b09a-821420894312",
+                    "url": "https://localhost/api/dcim/manufacturers/819ff875-95b2-4595-b09a-821420894312/",
+                    "name": "Cisco",
+                    "slug": "cisco",
+                    "display": "Cisco"
+                },
+                "model": "IOSv",
+                "slug": "iosv",
+                "display": "Cisco IOSv"
+            },
+            "device_role": {
+                "id": "a4027f8b-60a2-4c73-9de2-1d64c4ff4cb6",
+                "url": "https://localhost/api/dcim/device-roles/a4027f8b-60a2-4c73-9de2-1d64c4ff4cb6/",
+                "name": "Router",
+                "slug": "router",
+                "display": "Router"
+            },
+            "tenant": null,
+            "platform": {
+                "id": "98525687-3f7c-4413-933d-1b5602f1fa7d",
+                "url": "https://localhost/api/dcim/platforms/98525687-3f7c-4413-933d-1b5602f1fa7d/",
+                "name": "Cisco IOS",
+                "slug": "ios",
+                "display": "Cisco IOS"
+            },
+            "serial": "",
+            "asset_tag": null,
+            "site": {
+                "id": "ba9d7504-e17b-4b7a-b582-bc3800ebe680",
+                "url": "https://localhost/api/dcim/sites/ba9d7504-e17b-4b7a-b582-bc3800ebe680/",
+                "name": "GRB",
+                "slug": "grb",
+                "display": "GRB"
+            },
+            "rack": null,
+            "position": null,
+            "face": null,
+            "parent_device": null,
+            "status": {
+                "value": "active",
+                "label": "Active"
+            },
+            "primary_ip": null,
+            "primary_ip4": null,
+            "primary_ip6": null,
+            "cluster": null,
+            "virtual_chassis": null,
+            "vc_position": null,
+            "vc_priority": null,
+            "comments": "",
+            "local_context_data": null,
+            "tags": [],
+            "custom_fields": {},
+            "config_context": {},
+            "created": "2021-05-04",
+            "last_updated": "2021-05-04T20:42:32.056461Z",
+            "display": "grb-rtr01"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/tests/unit/adapters/nautobot_api/fixtures/inventory/platforms.json b/tests/unit/adapters/nautobot_api/fixtures/inventory/platforms.json
new file mode 100644
index 00000000..44c00ca1
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/fixtures/inventory/platforms.json
@@ -0,0 +1,111 @@
+{
+    "count": 5,
+    "next": null,
+    "previous": null,
+    "results": [
+        {
+            "id": "d89e745b-7eb3-4fbe-b3df-5aed8be7f8a5",
+            "url": "https://localhost/api/dcim/platforms/d89e745b-7eb3-4fbe-b3df-5aed8be7f8a5/",
+            "name": "Arista EOS",
+            "slug": "eos",
+            "manufacturer": {
+                "id": "eb3c3d44-8d1b-4940-acae-1374b2f2412b",
+                "url": "https://localhost/api/dcim/manufacturers/eb3c3d44-8d1b-4940-acae-1374b2f2412b/",
+                "name": "Arista",
+                "slug": "arista",
+                "display": "Arista"
+            },
+            "napalm_driver": "",
+            "napalm_args": null,
+            "description": "",
+            "device_count": 0,
+            "virtualmachine_count": 0,
+            "custom_fields": {},
+            "created": "2021-05-04",
+            "last_updated": "2021-05-04T20:41:59.525963Z",
+            "display": "Arista EOS"
+        },
+        {
+            "id": "24763d85-f0bc-46f0-a7ab-22b404c3d715",
+            "url": "https://localhost/api/dcim/platforms/24763d85-f0bc-46f0-a7ab-22b404c3d715/",
+            "name": "cisco_ios",
+            "slug": "cisco_ios",
+            "manufacturer": null,
+            "napalm_driver": "ios",
+            "napalm_args": null,
+            "description": "",
+            "device_count": 1,
+            "virtualmachine_count": 0,
+            "custom_fields": {},
+            "created": "2021-04-29",
+            "last_updated": "2021-04-29T18:47:08.801821Z",
+            "display": "cisco_ios"
+        },
+        {
+            "id": "98525687-3f7c-4413-933d-1b5602f1fa7d",
+            "url": "https://localhost/api/dcim/platforms/98525687-3f7c-4413-933d-1b5602f1fa7d/",
+            "name": "Cisco IOS",
+            "slug": "ios",
+            "manufacturer": {
+                "id": "819ff875-95b2-4595-b09a-821420894312",
+                "url": "https://localhost/api/dcim/manufacturers/819ff875-95b2-4595-b09a-821420894312/",
+                "name": "Cisco",
+                "slug": "cisco",
+                "display": "Cisco"
+            },
+            "napalm_driver": "ios_napalm",
+            "napalm_args": null,
+            "description": "",
+            "device_count": 2,
+            "virtualmachine_count": 0,
+            "custom_fields": {},
+            "created": "2021-05-04",
+            "last_updated": "2021-05-11T00:21:04.321809Z",
+            "display": "Cisco IOS"
+        },
+        {
+            "id": "4b68c8b7-9119-4fd1-8aaf-484e0ea98529",
+            "url": "https://localhost/api/dcim/platforms/4b68c8b7-9119-4fd1-8aaf-484e0ea98529/",
+            "name": "Cisco NXOS",
+            "slug": "nxos",
+            "manufacturer": {
+                "id": "819ff875-95b2-4595-b09a-821420894312",
+                "url": "https://localhost/api/dcim/manufacturers/819ff875-95b2-4595-b09a-821420894312/",
+                "name": "Cisco",
+                "slug": "cisco",
+                "display": "Cisco"
+            },
+            "napalm_driver": "nxos_napalm",
+            "napalm_args": null,
+            "description": "",
+            "device_count": 0,
+            "virtualmachine_count": 0,
+            "custom_fields": {},
+            "created": "2021-05-11",
+            "last_updated": "2021-05-11T01:12:42.847985Z",
+            "display": "Cisco NXOS"
+        },
+        {
+            "id": "fbeab173-ebfd-4a64-8876-bc36cfc4a51f",
+            "url": "https://localhost/api/dcim/platforms/fbeab173-ebfd-4a64-8876-bc36cfc4a51f/",
+            "name": "JUNOS",
+            "slug": "junos",
+            "manufacturer": {
+                "id": "58a880d4-4eb2-47db-af98-e48dfa46d98a",
+                "url": "https://localhost/api/dcim/manufacturers/58a880d4-4eb2-47db-af98-e48dfa46d98a/",
+                "name": "Juniper",
+                "slug": "juniper",
+                "display": "Juniper"
+            },
+            "napalm_driver": "",
+            "napalm_args": null,
+            "description": "",
+            "device_count": 0,
+            "virtualmachine_count": 0,
+            "custom_fields": {},
+            "created": "2021-05-04",
+            "last_updated": "2021-05-04T20:42:01.023249Z",
+            "display": "JUNOS"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/tests/unit/adapters/nautobot_api/fixtures/inventory/stack_devices.json b/tests/unit/adapters/nautobot_api/fixtures/inventory/stack_devices.json
new file mode 100644
index 00000000..524a906c
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/fixtures/inventory/stack_devices.json
@@ -0,0 +1,369 @@
+{
+    "count": 5,
+    "next": null,
+    "previous": null,
+    "results": [
+        {
+            "id": "121cb69d-bbff-4867-81e4-cbb2ba1bd93f",
+            "url": "https://localhost/api/dcim/devices/121cb69d-bbff-4867-81e4-cbb2ba1bd93f/",
+            "name": "grb-rtr01",
+            "device_type": {
+                "id": "599a9bd7-0a32-484d-b815-d9557bcc86e6",
+                "url": "https://localhost/api/dcim/device-types/599a9bd7-0a32-484d-b815-d9557bcc86e6/",
+                "manufacturer": {
+                    "id": "819ff875-95b2-4595-b09a-821420894312",
+                    "url": "https://localhost/api/dcim/manufacturers/819ff875-95b2-4595-b09a-821420894312/",
+                    "name": "Cisco",
+                    "slug": "cisco",
+                    "display": "Cisco"
+                },
+                "model": "IOSv",
+                "slug": "iosv",
+                "display": "Cisco IOSv"
+            },
+            "device_role": {
+                "id": "a4027f8b-60a2-4c73-9de2-1d64c4ff4cb6",
+                "url": "https://localhost/api/dcim/device-roles/a4027f8b-60a2-4c73-9de2-1d64c4ff4cb6/",
+                "name": "Router",
+                "slug": "router",
+                "display": "Router"
+            },
+            "tenant": null,
+            "platform": {
+                "id": "98525687-3f7c-4413-933d-1b5602f1fa7d",
+                "url": "https://localhost/api/dcim/platforms/98525687-3f7c-4413-933d-1b5602f1fa7d/",
+                "name": "Cisco IOS",
+                "slug": "ios",
+                "display": "Cisco IOS"
+            },
+            "serial": "",
+            "asset_tag": null,
+            "site": {
+                "id": "ba9d7504-e17b-4b7a-b582-bc3800ebe680",
+                "url": "https://localhost/api/dcim/sites/ba9d7504-e17b-4b7a-b582-bc3800ebe680/",
+                "name": "GRB",
+                "slug": "grb",
+                "display": "GRB"
+            },
+            "rack": null,
+            "position": null,
+            "face": null,
+            "parent_device": null,
+            "status": {
+                "value": "active",
+                "label": "Active"
+            },
+            "primary_ip": null,
+            "primary_ip4": null,
+            "primary_ip6": null,
+            "cluster": null,
+            "virtual_chassis": null,
+            "vc_position": null,
+            "vc_priority": null,
+            "comments": "",
+            "local_context_data": null,
+            "tags": [],
+            "custom_fields": {},
+            "config_context": {},
+            "created": "2021-05-04",
+            "last_updated": "2021-05-04T20:42:32.056461Z",
+            "display": "grb-rtr01"
+        },
+        {
+            "id": "07b4d653-c8fb-4473-8727-11eed93bfcbf",
+            "url": "https://localhost/api/dcim/devices/07b4d653-c8fb-4473-8727-11eed93bfcbf/",
+            "name": "msp-rtr01",
+            "device_type": {
+                "id": "fb72b94e-2872-4c50-aef4-fcb54d59d185",
+                "url": "https://localhost/api/dcim/device-types/fb72b94e-2872-4c50-aef4-fcb54d59d185/",
+                "manufacturer": {
+                    "id": "819ff875-95b2-4595-b09a-821420894312",
+                    "url": "https://localhost/api/dcim/manufacturers/819ff875-95b2-4595-b09a-821420894312/",
+                    "name": "Cisco",
+                    "slug": "cisco",
+                    "display": "Cisco"
+                },
+                "model": "nxosv",
+                "slug": "nxosv",
+                "display": "Cisco nxosv"
+            },
+            "device_role": {
+                "id": "a4027f8b-60a2-4c73-9de2-1d64c4ff4cb6",
+                "url": "https://localhost/api/dcim/device-roles/a4027f8b-60a2-4c73-9de2-1d64c4ff4cb6/",
+                "name": "Router",
+                "slug": "router",
+                "display": "Router"
+            },
+            "tenant": null,
+            "platform": {
+                "id": "4b68c8b7-9119-4fd1-8aaf-484e0ea98529",
+                "url": "https://localhost/api/dcim/platforms/4b68c8b7-9119-4fd1-8aaf-484e0ea98529/",
+                "name": "Cisco NXOS",
+                "slug": "nxos",
+                "display": "Cisco NXOS"
+            },
+            "serial": "",
+            "asset_tag": null,
+            "site": {
+                "id": "e13825ac-6996-44ab-9a41-2f35a8b04466",
+                "url": "https://localhost/api/dcim/sites/e13825ac-6996-44ab-9a41-2f35a8b04466/",
+                "name": "MSP",
+                "slug": "msp",
+                "display": "MSP"
+            },
+            "rack": null,
+            "position": null,
+            "face": null,
+            "parent_device": null,
+            "status": {
+                "value": "active",
+                "label": "Active"
+            },
+            "primary_ip": null,
+            "primary_ip4": null,
+            "primary_ip6": null,
+            "cluster": null,
+            "virtual_chassis": null,
+            "vc_position": null,
+            "vc_priority": null,
+            "comments": "",
+            "local_context_data": null,
+            "tags": [],
+            "custom_fields": {},
+            "config_context": {},
+            "created": "2021-05-04",
+            "last_updated": "2021-05-11T01:13:34.799672Z",
+            "display": "msp-rtr01"
+        },
+        {
+            "id": "07aa45f5-ba14-420b-86cd-c4157f2a0fee",
+            "url": "https://localhost/api/dcim/devices/07aa45f5-ba14-420b-86cd-c4157f2a0fee/",
+            "name": "stack01",
+            "device_type": {
+                "id": "ee3fa777-7c5c-42d4-a59c-68523b19aa66",
+                "url": "https://localhost/api/dcim/device-types/ee3fa777-7c5c-42d4-a59c-68523b19aa66/",
+                "manufacturer": {
+                    "id": "819ff875-95b2-4595-b09a-821420894312",
+                    "url": "https://localhost/api/dcim/manufacturers/819ff875-95b2-4595-b09a-821420894312/",
+                    "name": "Cisco",
+                    "slug": "cisco",
+                    "display": "Cisco"
+                },
+                "model": "9300-48U",
+                "slug": "9300-48u",
+                "display": "Cisco 9300-48U"
+            },
+            "device_role": {
+                "id": "45f59e50-60ec-468a-ae7a-1854e838ba22",
+                "url": "https://localhost/api/dcim/device-roles/45f59e50-60ec-468a-ae7a-1854e838ba22/",
+                "name": "Switch",
+                "slug": "switch",
+                "display": "Switch"
+            },
+            "tenant": null,
+            "platform": {
+                "id": "24763d85-f0bc-46f0-a7ab-22b404c3d715",
+                "url": "https://localhost/api/dcim/platforms/24763d85-f0bc-46f0-a7ab-22b404c3d715/",
+                "name": "cisco_ios",
+                "slug": "cisco_ios",
+                "display": "cisco_ios"
+            },
+            "serial": "",
+            "asset_tag": null,
+            "site": {
+                "id": "38c8f069-9bdb-4a2c-9377-0f4015b50164",
+                "url": "https://localhost/api/dcim/sites/38c8f069-9bdb-4a2c-9377-0f4015b50164/",
+                "name": "DEN",
+                "slug": "den",
+                "display": "DEN"
+            },
+            "rack": null,
+            "position": null,
+            "face": null,
+            "parent_device": null,
+            "status": {
+                "value": "active",
+                "label": "Active"
+            },
+            "primary_ip": null,
+            "primary_ip4": null,
+            "primary_ip6": null,
+            "cluster": null,
+            "virtual_chassis": {
+                "id": "1ecc76df-bd4f-48c9-9103-a29a1bfd0338",
+                "name": "stack01-vc",
+                "url": "https://localhost/api/dcim/virtual-chassis/1ecc76df-bd4f-48c9-9103-a29a1bfd0338/",
+                "master": {
+                    "id": "07aa45f5-ba14-420b-86cd-c4157f2a0fee",
+                    "url": "https://localhost/api/dcim/devices/07aa45f5-ba14-420b-86cd-c4157f2a0fee/",
+                    "name": "stack01",
+                    "display": "stack01"
+                },
+                "display": "stack01-vc"
+            },
+            "vc_position": 1,
+            "vc_priority": 1,
+            "comments": "",
+            "local_context_data": null,
+            "tags": [],
+            "custom_fields": {},
+            "config_context": {
+                "ansible_network_os": "ios"
+            },
+            "created": "2021-05-11",
+            "last_updated": "2021-05-11T01:31:45.653033Z",
+            "display": "stack01"
+        },
+        {
+            "id": "efdd5c6d-bbff-43ab-b88e-1cd39c75ffdd",
+            "url": "https://localhost/api/dcim/devices/efdd5c6d-bbff-43ab-b88e-1cd39c75ffdd/",
+            "name": "stack01:2",
+            "device_type": {
+                "id": "ee3fa777-7c5c-42d4-a59c-68523b19aa66",
+                "url": "https://localhost/api/dcim/device-types/ee3fa777-7c5c-42d4-a59c-68523b19aa66/",
+                "manufacturer": {
+                    "id": "819ff875-95b2-4595-b09a-821420894312",
+                    "url": "https://localhost/api/dcim/manufacturers/819ff875-95b2-4595-b09a-821420894312/",
+                    "name": "Cisco",
+                    "slug": "cisco",
+                    "display": "Cisco"
+                },
+                "model": "9300-48U",
+                "slug": "9300-48u",
+                "display": "Cisco 9300-48U"
+            },
+            "device_role": {
+                "id": "45f59e50-60ec-468a-ae7a-1854e838ba22",
+                "url": "https://localhost/api/dcim/device-roles/45f59e50-60ec-468a-ae7a-1854e838ba22/",
+                "name": "Switch",
+                "slug": "switch",
+                "display": "Switch"
+            },
+            "tenant": null,
+            "platform": null,
+            "serial": "",
+            "asset_tag": null,
+            "site": {
+                "id": "38c8f069-9bdb-4a2c-9377-0f4015b50164",
+                "url": "https://localhost/api/dcim/sites/38c8f069-9bdb-4a2c-9377-0f4015b50164/",
+                "name": "DEN",
+                "slug": "den",
+                "display": "DEN"
+            },
+            "rack": null,
+            "position": null,
+            "face": null,
+            "parent_device": null,
+            "status": {
+                "value": "active",
+                "label": "Active"
+            },
+            "primary_ip": null,
+            "primary_ip4": null,
+            "primary_ip6": null,
+            "cluster": null,
+            "virtual_chassis": {
+                "id": "1ecc76df-bd4f-48c9-9103-a29a1bfd0338",
+                "name": "stack01-vc",
+                "url": "https://localhost/api/dcim/virtual-chassis/1ecc76df-bd4f-48c9-9103-a29a1bfd0338/",
+                "master": {
+                    "id": "07aa45f5-ba14-420b-86cd-c4157f2a0fee",
+                    "url": "https://localhost/api/dcim/devices/07aa45f5-ba14-420b-86cd-c4157f2a0fee/",
+                    "name": "stack01",
+                    "display": "stack01"
+                },
+                "display": "stack01-vc"
+            },
+            "vc_position": 2,
+            "vc_priority": null,
+            "comments": "",
+            "local_context_data": null,
+            "tags": [],
+            "custom_fields": {},
+            "config_context": {},
+            "created": "2021-05-11",
+            "last_updated": "2021-05-11T01:24:00.439442Z",
+            "display": "stack01:2"
+        },
+        {
+            "id": "794d8dc0-ce43-4857-a70f-3020e8aa861b",
+            "url": "https://localhost/api/dcim/devices/794d8dc0-ce43-4857-a70f-3020e8aa861b/",
+            "name": "sw01",
+            "device_type": {
+                "id": "d6f5a5a9-93b8-4626-813d-247051887fb6",
+                "url": "https://localhost/api/dcim/device-types/d6f5a5a9-93b8-4626-813d-247051887fb6/",
+                "manufacturer": {
+                    "id": "819ff875-95b2-4595-b09a-821420894312",
+                    "url": "https://localhost/api/dcim/manufacturers/819ff875-95b2-4595-b09a-821420894312/",
+                    "name": "Cisco",
+                    "slug": "cisco",
+                    "display": "Cisco"
+                },
+                "model": "WS-C3750G-48PS",
+                "slug": "ws-c3750g-48ps",
+                "display": "Cisco WS-C3750G-48PS"
+            },
+            "device_role": {
+                "id": "d520822f-7841-480b-bb95-c449d26ca5d0",
+                "url": "https://localhost/api/dcim/device-roles/d520822f-7841-480b-bb95-c449d26ca5d0/",
+                "name": "network",
+                "slug": "network",
+                "display": "network"
+            },
+            "tenant": null,
+            "platform": {
+                "id": "24763d85-f0bc-46f0-a7ab-22b404c3d715",
+                "url": "https://localhost/api/dcim/platforms/24763d85-f0bc-46f0-a7ab-22b404c3d715/",
+                "name": "cisco_ios",
+                "slug": "cisco_ios",
+                "display": "cisco_ios"
+            },
+            "serial": "FOC0948Y2RB",
+            "asset_tag": null,
+            "site": {
+                "id": "e13825ac-6996-44ab-9a41-2f35a8b04466",
+                "url": "https://localhost/api/dcim/sites/e13825ac-6996-44ab-9a41-2f35a8b04466/",
+                "name": "MSP",
+                "slug": "msp",
+                "display": "MSP"
+            },
+            "rack": null,
+            "position": null,
+            "face": null,
+            "parent_device": null,
+            "status": {
+                "value": "active",
+                "label": "Active"
+            },
+            "primary_ip": {
+                "id": "4e829ee7-abae-435a-bd3c-d6a87a135269",
+                "url": "https://localhost/api/ipam/ip-addresses/4e829ee7-abae-435a-bd3c-d6a87a135269/",
+                "family": 4,
+                "address": "10.250.0.2/24",
+                "display": "10.250.0.2/24"
+            },
+            "primary_ip4": {
+                "id": "4e829ee7-abae-435a-bd3c-d6a87a135269",
+                "url": "https://localhost/api/ipam/ip-addresses/4e829ee7-abae-435a-bd3c-d6a87a135269/",
+                "family": 4,
+                "address": "10.250.0.2/24",
+                "display": "10.250.0.2/24"
+            },
+            "primary_ip6": null,
+            "cluster": null,
+            "virtual_chassis": null,
+            "vc_position": null,
+            "vc_priority": null,
+            "comments": "",
+            "local_context_data": null,
+            "tags": [],
+            "custom_fields": {},
+            "config_context": {
+                "ansible_network_os": "ios"
+            },
+            "created": "2021-04-29",
+            "last_updated": "2021-04-29T18:47:33.170873Z",
+            "display": "sw01"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/tests/unit/adapters/nautobot_api/fixtures/ip_address.json b/tests/unit/adapters/nautobot_api/fixtures/ip_address.json
new file mode 100644
index 00000000..8b4338b3
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/fixtures/ip_address.json
@@ -0,0 +1,39 @@
+{
+    "id": "2c6f4d82-e8e4-48ca-a62f-abf8586ff82a",
+    "url": "http://localhost/api/ipam/ip-addresses/2c6f4d82-e8e4-48ca-a62f-abf8586ff82a/",
+    "family": {
+        "value": 4,
+        "label": "IPv4"
+    },
+    "address": "10.255.0.69/30",
+    "vrf": null,
+    "tenant": null,
+    "status": {
+        "value": "active",
+        "label": "Active"
+    },
+    "role": null,
+    "assigned_object_type": "dcim.interface",
+    "assigned_object_id": "3f5c0760-54ec-4081-9b27-afbe49e824aa",
+    "assigned_object": {
+        "id": "3f5c0760-54ec-4081-9b27-afbe49e824aa",
+        "url": "http://localhost/api/dcim/interfaces/3f5c0760-54ec-4081-9b27-afbe49e824aa/",
+        "device": {
+            "id": "044c4bc9-c88c-4d8b-abb7-9dc1835696d4",
+            "url": "http://localhost/api/dcim/devices/e0633a07-c3e2-41b0-a1df-4627392acf0a/",
+            "name": "HQ-CORE-SW02",
+            "display_name": "HQ-CORE-SW02"
+        },
+        "name": "TenGigabitEthernet1/0/1",
+        "cable": null,
+        "connection_status": null
+    },
+    "nat_inside": null,
+    "nat_outside": null,
+    "dns_name": "",
+    "description": "",
+    "tags": [],
+    "custom_fields": {},
+    "created": "2020-11-05",
+    "last_updated": "2020-11-05T22:59:40.561732Z"
+}
\ No newline at end of file
diff --git a/tests/unit/adapters/nautobot_api/fixtures/prefix_no_vlan.json b/tests/unit/adapters/nautobot_api/fixtures/prefix_no_vlan.json
new file mode 100644
index 00000000..88012ef3
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/fixtures/prefix_no_vlan.json
@@ -0,0 +1,37 @@
+{
+    "id": "3e1d0bf7-43de-493e-901e-661647aac2af",
+    "url": "https://localhost/api/ipam/prefixes/3e1d0bf7-43de-493e-901e-661647aac2af/",
+    "family": {
+        "value": 4,
+        "label": "IPv4"
+    },
+    "prefix": "10.0.0.0/16",
+    "site": {
+        "id": "f54adca8-79a8-40e8-900f-d5dfe48c64eb",
+        "url": "https://localhost/api/dcim/sites/f54adca8-79a8-40e8-900f-d5dfe48c64eb/",
+        "name": "atl",
+        "slug": "atl",
+        "display": "atl"
+    },
+    "vrf": null,
+    "tenant": null,
+    "vlan": null,
+    "status": {
+        "value": "container",
+        "label": "Container"
+    },
+    "role": {
+        "id": "8fc01502-3a94-436b-96d9-a0a4dd1dd8d8",
+        "url": "https://localhost/api/ipam/roles/8fc01502-3a94-436b-96d9-a0a4dd1dd8d8/",
+        "name": "pop",
+        "slug": "pop",
+        "display": "pop"
+    },
+    "is_pool": false,
+    "description": "",
+    "tags": [],
+    "custom_fields": {},
+    "created": "2021-04-19",
+    "last_updated": "2021-04-19T15:26:43.578526",
+    "display": "10.0.0.0/16"
+}
\ No newline at end of file
diff --git a/tests/unit/adapters/nautobot_api/fixtures/prefix_vlan.json b/tests/unit/adapters/nautobot_api/fixtures/prefix_vlan.json
new file mode 100644
index 00000000..af01b248
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/fixtures/prefix_vlan.json
@@ -0,0 +1,28 @@
+{
+    "id": "71d0b310-f32b-4cc0-b940-afb8ab50b985",
+    "url": "https://demo.nautobot.com/api/ipam/prefixes/71d0b310-f32b-4cc0-b940-afb8ab50b985/",
+    "family": {
+        "value": 4,
+        "label": "IPv4"
+    },
+    "prefix": "10.0.0.0/24",
+    "site": null,
+    "vrf": null,
+    "tenant": null,
+    "vlan": {
+        "id": "6aad8891-a235-4eac-a685-f4b3012adab3",
+        "url": "https://demo.nautobot.com/api/ipam/vlans/6aad8891-a235-4eac-a685-f4b3012adab3/",
+        "vid": 1000,
+        "name": "atl-101-server",
+        "display": "atl-101-server (1000)"
+    },
+    "status": null,
+    "role": null,
+    "is_pool": false,
+    "description": "",
+    "tags": [],
+    "custom_fields": {},
+    "created": "2021-04-19",
+    "last_updated": "2021-04-19T15:26:48.443602",
+    "display": "10.0.0.0/24"
+}
\ No newline at end of file
diff --git a/tests/unit/adapters/nautobot_api/fixtures/tag_01.json b/tests/unit/adapters/nautobot_api/fixtures/tag_01.json
new file mode 100644
index 00000000..c491b343
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/fixtures/tag_01.json
@@ -0,0 +1,12 @@
+{
+    "id": "3fed3ac5-c623-493c-b029-87487830d159",
+    "url": "https://mock_nautobot/api/extras/tags/3fed3ac5-c623-493c-b029-87487830d159/",
+    "name": "device=dev2",
+    "slug": "device__device2",
+    "color": "9e9e9e",
+    "description": "",
+    "custom_fields": {},
+    "created": "2021-04-29",
+    "last_updated": "2021-04-29T19:57:15.377817Z",
+    "display": "device=dev2"
+}
\ No newline at end of file
diff --git a/tests/unit/adapters/nautobot_api/fixtures/tag_01_list.json b/tests/unit/adapters/nautobot_api/fixtures/tag_01_list.json
new file mode 100644
index 00000000..b89a4644
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/fixtures/tag_01_list.json
@@ -0,0 +1,20 @@
+{
+    "count": 1,
+    "next": null,
+    "previous": null,
+    "results": [
+        {
+            "id": "eb697742-364d-4714-b585-a267c64d7720",
+            "url": "https://nautobot_mock/api/extras/tags/eb697742-364d-4714-b585-a267c64d7720/",
+            "name": "device=dev1",
+            "slug": "device_dev1",
+            "color": "9e9e9e",
+            "description": "",
+            "tagged_items": 0,
+            "custom_fields": {},
+            "created": "2021-04-29",
+            "last_updated": "2021-04-29T18:53:09.097320Z",
+            "display": "device=dev1"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/tests/unit/adapters/nautobot_api/fixtures/vlan_101_no_tag.json b/tests/unit/adapters/nautobot_api/fixtures/vlan_101_no_tag.json
new file mode 100644
index 00000000..50575895
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/fixtures/vlan_101_no_tag.json
@@ -0,0 +1,31 @@
+{
+    "id": "eb697742-364d-4714-b585-a267c64d7720",
+    "site": {
+        "id": 67,
+        "url": "http://localhost/api/dcim/sites/67/",
+        "name": "DC1",
+        "slug": "dc1"
+    },
+    "group": null,
+    "vid": 101,
+    "name": "R101",
+    "tenant": null,
+    "status": {
+        "value": "active",
+        "label": "Active",
+        "id": 1
+    },
+    "role": {
+        "id": 3,
+        "url": "http://localhost/api/ipam/roles/3/",
+        "name": "Access",
+        "slug": "access"
+    },
+    "description": "",
+    "tags": [],
+    "display_name": "101 (R101)",
+    "custom_fields": {},
+    "created": "2019-11-27",
+    "last_updated": "2019-11-27T19:15:39.988000Z",
+    "prefix_count": 1
+}
\ No newline at end of file
diff --git a/tests/unit/adapters/nautobot_api/fixtures/vlan_101_tags_01.json b/tests/unit/adapters/nautobot_api/fixtures/vlan_101_tags_01.json
new file mode 100644
index 00000000..d61a1efa
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/fixtures/vlan_101_tags_01.json
@@ -0,0 +1,53 @@
+{
+    "id": "eb697742-364d-4714-b585-a267c64d7720",
+    "site": {
+        "id": 67,
+        "url": "http://localhost/api/dcim/sites/67/",
+        "name": "DC1",
+        "slug": "dc1"
+    },
+    "group": null,
+    "vid": 101,
+    "name": "R101",
+    "tenant": null,
+    "status": {
+        "value": "active",
+        "label": "Active",
+        "id": 1
+    },
+    "role": {
+        "id": 3,
+        "url": "http://localhost/api/ipam/roles/3/",
+        "name": "Access",
+        "slug": "access"
+    },
+    "description": "",
+    "tags": [
+        {
+            "id": "d0c52a6c-b3e9-4234-98ef-ee9b76ca31db",
+            "url": "http://0.0.0.0:8000/api/extras/tags/1/",
+            "name": "device=devA",
+            "slug": "device_devA",
+            "color": "9e9e9e"
+        },
+        {
+            "id": "999121c7-37d6-44a8-83f9-61706e915bde",
+            "url": "http://0.0.0.0:8000/api/extras/tags/2/",
+            "name": "notadevice",
+            "slug": "notadevice",
+            "color": "9e9e9e"
+        },
+        {
+            "id": "fd6809fa-26cf-47b2-8742-974cd4d22ca9",
+            "url": "http://0.0.0.0:8000/api/extras/tags/3/",
+            "name": "device=devB",
+            "slug": "device_devB",
+            "color": "9e9e9e"
+        }
+    ],
+    "display_name": "101 (R101)",
+    "custom_fields": {},
+    "created": "2019-11-27",
+    "last_updated": "2019-11-27T19:15:39.988000Z",
+    "prefix_count": 1
+}
\ No newline at end of file
diff --git a/tests/unit/adapters/nautobot_api/models/test_nautobot_device.py b/tests/unit/adapters/nautobot_api/models/test_nautobot_device.py
new file mode 100644
index 00000000..57a83a34
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/models/test_nautobot_device.py
@@ -0,0 +1,40 @@
+"""test for NautobotDevice model."""
+import os
+import yaml
+
+from network_importer.adapters.nautobot_api.models import NautobotDevice
+
+ROOT = os.path.abspath(os.path.dirname(__file__))
+
+
+def test_nautobot_get_device_tag_id():
+
+    device = NautobotDevice(
+        name="dev12", site_name="HQ", remote_id=32, device_tag_id="eb697742-364d-4714-b585-a267c64d7720"
+    )
+    assert device.get_device_tag_id() == "eb697742-364d-4714-b585-a267c64d7720"
+
+
+def test_nautobot_get_device_tag_id_get_tag(requests_mock, nautobot_api_base):
+
+    data = yaml.safe_load(open(f"{ROOT}/../fixtures/tag_01_list.json"))
+    requests_mock.get("http://mock_nautobot/api/extras/tags/?name=device%3Ddev1", json=data, status_code=200)
+
+    device = NautobotDevice(name="dev1", site_name="HQ", remote_id=32)
+    nautobot_api_base.add(device)
+
+    assert device.get_device_tag_id() == "eb697742-364d-4714-b585-a267c64d7720"
+
+
+def test_get_device_tag_id_create_tag(requests_mock, nautobot_api_base, empty_nautobot_query):
+
+    data = yaml.safe_load(open(f"{ROOT}/../fixtures/tag_01.json"))
+    requests_mock.get(
+        "http://mock_nautobot/api/extras/tags/?name=device%3Ddev1", json=empty_nautobot_query, status_code=200
+    )
+    requests_mock.post("http://mock_nautobot/api/extras/tags/", json=data, status_code=201)
+
+    device = NautobotDevice(name="dev1", site_name="HQ", remote_id=32)
+    nautobot_api_base.add(device)
+
+    assert device.get_device_tag_id() == "3fed3ac5-c623-493c-b029-87487830d159"
diff --git a/tests/unit/adapters/nautobot_api/models/test_nautobot_interface.py b/tests/unit/adapters/nautobot_api/models/test_nautobot_interface.py
new file mode 100644
index 00000000..3698ffb5
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/models/test_nautobot_interface.py
@@ -0,0 +1,231 @@
+"""test for NautobotInterface model."""
+import os
+import pytest
+
+from diffsync.exceptions import ObjectNotFound
+
+import network_importer.config as config
+from network_importer.adapters.nautobot_api.models import NautobotInterface, NautobotDevice, NautobotVlan
+from network_importer.adapters.nautobot_api.exceptions import NautobotObjectNotValid
+
+ROOT = os.path.abspath(os.path.dirname(__file__))
+
+# pylint: disable=pointless-statement
+
+
+def assert_baseline(data):
+    """Check that name and device are always present in the response."""
+    assert "name" in data
+    assert data["name"] == "ge-0/0/0"
+    assert "device" in data
+    assert data["device"] == 29
+
+
+def test_translate_attrs_for_nautobot_wrong_device(nautobot_api_base):
+
+    intf = NautobotInterface(device_name="HQ-CORE-SW01", name="ge-0/0/0")
+    nautobot_api_base.add(intf)
+
+    with pytest.raises(ObjectNotFound):
+        intf.translate_attrs_for_nautobot({})
+
+
+def test_translate_attrs_for_nautobot_device_no_remote_id(nautobot_api_base):
+
+    nautobot_api_base.add(NautobotDevice(name="HQ-CORE-SW01", site_name="nyc"))
+    intf = NautobotInterface(device_name="HQ-CORE-SW01", name="ge-0/0/0")
+    nautobot_api_base.add(intf)
+
+    with pytest.raises(NautobotObjectNotValid):
+        intf.translate_attrs_for_nautobot({})
+
+
+def test_translate_attrs_for_nautobot_no_attrs(nautobot_api_base):
+
+    config.load(config_data=dict(main=dict(import_vlans=True, backend="nautobot")))
+    intf = NautobotInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
+    nautobot_api_base.add(intf)
+
+    params = intf.translate_attrs_for_nautobot({})
+
+    assert sorted(list(params.keys())) == ["description", "device", "lag", "name", "type"]
+    assert params["name"] == "ge-0/0/0"
+    assert params["device"] == "e0633a07-c3e2-41b0-a1df-4627392acf0a"
+    assert params["type"] == "other"
+    assert params["lag"] is None
+    assert params["description"] == ""
+
+
+def test_translate_attrs_for_nautobot_type(nautobot_api_base):
+
+    config.load(config_data=dict(main=dict(import_vlans=True, backend="nautobot")))
+    intf = NautobotInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
+    nautobot_api_base.add(intf)
+
+    params = intf.translate_attrs_for_nautobot({"is_lag": True, "is_virtual": True})
+    assert_baseline
+    assert params["type"] == "lag"
+
+    params = intf.translate_attrs_for_nautobot({"is_lag": False, "is_virtual": True})
+    assert_baseline
+    assert params["type"] == "virtual"
+
+    params = intf.translate_attrs_for_nautobot({"is_lag": False, "is_virtual": False})
+    assert_baseline
+    assert params["type"] == "other"
+
+
+def test_translate_attrs_for_nautobot_description(nautobot_api_base):
+
+    config.load(config_data=dict(main=dict(import_vlans=True, backend="nautobot")))
+    intf = NautobotInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
+    nautobot_api_base.add(intf)
+
+    params = intf.translate_attrs_for_nautobot({})
+    assert_baseline
+    assert "description" in params
+    assert params["description"] == ""
+
+    params = intf.translate_attrs_for_nautobot({"description": "my_description"})
+    assert_baseline
+    assert params["description"] == "my_description"
+
+    params = intf.translate_attrs_for_nautobot({"description": None})
+    assert_baseline
+    assert params["description"] == ""
+    assert isinstance(params["description"], str)
+
+
+def test_translate_attrs_for_nautobot_mode(nautobot_api_base):
+
+    config.load(config_data=dict(main=dict(import_vlans=True, backend="nautobot")))
+    intf = NautobotInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
+    nautobot_api_base.add(intf)
+
+    params = intf.translate_attrs_for_nautobot({})
+    assert_baseline
+    assert "mode" not in params
+
+    params = intf.translate_attrs_for_nautobot({"switchport_mode": "ACCESS"})
+    assert_baseline
+    assert params["mode"] == "access"
+
+    params = intf.translate_attrs_for_nautobot({"switchport_mode": "TRUNK"})
+    assert_baseline
+    assert params["mode"] == "tagged"
+
+    params = intf.translate_attrs_for_nautobot({"switchport_mode": "NOTSUPPORTED"})
+    assert_baseline
+    assert "mode" not in params
+
+
+def test_translate_attrs_for_nautobot_vlan(nautobot_api_base):
+
+    vlan = NautobotVlan(vid=100, site_name="HQ", remote_id="601077ce-ac88-4b36-bbc7-23d655dc3958")
+    nautobot_api_base.add(vlan)
+
+    config.load(config_data=dict(main=dict(import_vlans=True, backend="nautobot")))
+    intf = NautobotInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
+    nautobot_api_base.add(intf)
+
+    params = intf.translate_attrs_for_nautobot({})
+    assert_baseline
+    assert "mode" not in params
+
+    params = intf.translate_attrs_for_nautobot(
+        {"switchport_mode": "ACCESS", "mode": "ACCESS", "access_vlan": "HQ__111"}
+    )
+    assert_baseline
+    assert params["mode"] == "access"
+    assert params["untagged_vlan"] == "464a2de3-fd5e-4b65-a58d-e0a2a617c12e"
+
+    params = intf.translate_attrs_for_nautobot({"switchport_mode": "ACCESS", "mode": "ACCESS", "access_vlan": None})
+    assert_baseline
+    assert params["mode"] == "access"
+    assert params["untagged_vlan"] is None
+
+    params = intf.translate_attrs_for_nautobot({"switchport_mode": "ACCESS", "mode": "ACCESS"})
+    assert_baseline
+    assert params["mode"] == "access"
+    assert params["untagged_vlan"] is None
+
+    params = intf.translate_attrs_for_nautobot(
+        {"switchport_mode": "TRUNK", "mode": "TRUNK", "allowed_vlans": ["HQ__111", "HQ__100"]}
+    )
+    assert_baseline
+    assert params["mode"] == "tagged"
+    assert params["tagged_vlans"] == ["464a2de3-fd5e-4b65-a58d-e0a2a617c12e", "601077ce-ac88-4b36-bbc7-23d655dc3958"]
+
+    params = intf.translate_attrs_for_nautobot({"switchport_mode": "TRUNK", "mode": "TRUNK"})
+    assert_baseline
+    assert params["mode"] == "tagged"
+    assert params["tagged_vlans"] == []
+
+    params = intf.translate_attrs_for_nautobot({"switchport_mode": "TRUNK", "mode": "L3_SUB_VLAN"})
+    assert_baseline
+    assert params["mode"] == "tagged"
+    assert params["tagged_vlans"] == []
+
+
+def test_translate_attrs_for_nautobot_vlan_false(nautobot_api_base):
+
+    config.load(config_data=dict(main=dict(import_vlans=False, backend="nautobot")))
+    intf = NautobotInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
+    nautobot_api_base.add(intf)
+
+    params = intf.translate_attrs_for_nautobot(
+        {"switchport_mode": "TRUNK", "mode": "TRUNK", "allowed_vlans": ["HQ__111", "HQ__100"]}
+    )
+    assert_baseline
+    assert "tagged_vlans" not in params
+    assert "untagged_vlan" not in params
+
+    params = intf.translate_attrs_for_nautobot(
+        {"switchport_mode": "ACCESS", "mode": "ACCESS", "access_vlan": "HQ__111"}
+    )
+    assert_baseline
+    assert "tagged_vlans" not in params
+    assert "untagged_vlan" not in params
+
+
+def test_translate_attrs_for_nautobot_vlan_no(nautobot_api_base):
+
+    config.load(config_data=dict(main=dict(import_vlans="no", backend="nautobot")))
+    intf = NautobotInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
+    nautobot_api_base.add(intf)
+
+    params = intf.translate_attrs_for_nautobot(
+        {"switchport_mode": "TRUNK", "mode": "TRUNK", "allowed_vlans": ["HQ__111", "HQ__100"]}
+    )
+    assert_baseline
+    assert "tagged_vlans" not in params
+    assert "untagged_vlan" not in params
+
+    params = intf.translate_attrs_for_nautobot(
+        {"switchport_mode": "ACCESS", "mode": "ACCESS", "access_vlan": "HQ__111"}
+    )
+    assert_baseline
+    assert "tagged_vlans" not in params
+    assert "untagged_vlan" not in params
+
+
+def test_translate_attrs_for_nautobot_lag_member(nautobot_api_base):
+
+    parent = NautobotInterface(
+        device_name="HQ-CORE-SW01", name="ge-0/0/4", remote_id="ea085648-5684-4362-a8dd-edfa151faaec"
+    )
+    nautobot_api_base.add(parent)
+
+    config.load(config_data=dict(main=dict(import_vlans=False, backend="nautobot")))
+    intf = NautobotInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
+    nautobot_api_base.add(intf)
+
+    params = intf.translate_attrs_for_nautobot({"is_lag_member": True, "parent": "HQ-CORE-SW01__ge-0/0/4"})
+    assert_baseline
+    assert "lag" in params
+    assert params["lag"] == "ea085648-5684-4362-a8dd-edfa151faaec"
+
+    params = intf.translate_attrs_for_nautobot({"is_lag_member": False, "parent": "HQ-CORE-SW01__ge-0/0/4"})
+    assert_baseline
+    assert "lag" in params
+    assert params["lag"] is None
diff --git a/tests/unit/adapters/nautobot_api/models/test_nautobot_ipaddress.py b/tests/unit/adapters/nautobot_api/models/test_nautobot_ipaddress.py
new file mode 100644
index 00000000..d8fd7e60
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/models/test_nautobot_ipaddress.py
@@ -0,0 +1,87 @@
+"""test for NautobotIPAddress model."""
+import os
+import yaml
+import pynautobot
+
+from network_importer.adapters.nautobot_api.models import NautobotIPAddress
+
+ROOT = os.path.abspath(os.path.dirname(__file__))
+
+
+def test_translate_attrs_for_nautobot_with_intf(nautobot_api_base):
+
+    ipaddr = NautobotIPAddress(
+        address="10.10.10.1/24",
+        device_name="HQ-CORE-SW02",
+        interface_name="TenGigabitEthernet1/0/1",
+        remote_id="2c6f4d82-e8e4-48ca-a62f-abf8586ff82a",
+    )
+    nautobot_api_base.add(ipaddr)
+    params = ipaddr.translate_attrs_for_nautobot()
+
+    assert "address" in params
+    assert params["address"] == "10.10.10.1/24"
+    assert "assigned_object_type" in params
+    assert params["assigned_object_type"] == "dcim.interface"
+    assert "assigned_object_id" in params
+    assert params["assigned_object_id"] == "fecc1d8f-99b1-491d-9bdf-1dcb394e27a1"
+
+
+def test_translate_attrs_for_nautobot_wo_intf(nautobot_api_base):
+
+    ipaddr = NautobotIPAddress(
+        address="10.10.10.1/24",
+        device_name="HQ-CORE-SW02",
+        interface_name="TenGigabitEthernet1/0/2",
+        remote_id="2c6f4d82-e8e4-48ca-a62f-abf8586ff82a",
+    )
+    nautobot_api_base.add(ipaddr)
+    params = ipaddr.translate_attrs_for_nautobot()
+
+    assert "address" in params
+    assert params["address"] == "10.10.10.1/24"
+    assert "assigned_object_type" not in params
+    assert "assigned_object_id" not in params
+
+
+def test_create_from_pynautobot(nautobot_api_base):
+    api = pynautobot.api(url="http://mock", token="1234567890")
+    data = yaml.safe_load(open(f"{ROOT}/../fixtures/ip_address.json"))
+    pnb = pynautobot.core.response.Record(values=data, api=api, endpoint=1)
+
+    ipaddr = NautobotIPAddress.create_from_pynautobot(diffsync=nautobot_api_base, obj=pnb, device_name="HQ-CORE-SW02")
+
+    assert ipaddr.interface_name == "TenGigabitEthernet1/0/1"
+    assert ipaddr.device_name == "HQ-CORE-SW02"
+
+
+def test_create_ip_address_interface(requests_mock, nautobot_api_base):
+
+    with open(f"{ROOT}/../fixtures/ip_address.json") as file:
+        data = yaml.safe_load(file)
+
+    requests_mock.post("http://mock_nautobot/api/ipam/ip-addresses/", json=data, status_code=201)
+    ip_address = NautobotIPAddress.create(
+        diffsync=nautobot_api_base,
+        ids=dict(address="10.63.0.2/31", interface_name="TenGigabitEthernet1/0/1", device_name="HQ-CORE-SW02"),
+        attrs=dict(),
+    )
+
+    assert isinstance(ip_address, NautobotIPAddress) is True
+    assert ip_address.remote_id == "2c6f4d82-e8e4-48ca-a62f-abf8586ff82a"
+
+
+def test_create_ip_address_no_interface(requests_mock, nautobot_api_base):
+
+    with open(f"{ROOT}/../fixtures/ip_address.json") as file:
+        data = yaml.safe_load(file)
+
+    requests_mock.post("http://mock_nautobot/api/ipam/ip-addresses/", json=data, status_code=201)
+    ip_address = NautobotIPAddress.create(
+        diffsync=nautobot_api_base,
+        ids=dict(address="10.63.0.2/31", interface_name="TenGigabitEthernet1/0/1", device_name="HQ-CORE-SW02"),
+        attrs=dict(),
+    )
+
+    assert isinstance(ip_address, NautobotIPAddress) is True
+    assert ip_address.remote_id == "2c6f4d82-e8e4-48ca-a62f-abf8586ff82a"
diff --git a/tests/unit/adapters/nautobot_api/models/test_nautobot_prefix.py b/tests/unit/adapters/nautobot_api/models/test_nautobot_prefix.py
new file mode 100644
index 00000000..0f795f31
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/models/test_nautobot_prefix.py
@@ -0,0 +1,134 @@
+"""test for NautobotPrefix model."""
+import os
+import yaml
+
+from network_importer.adapters.nautobot_api.models import NautobotPrefix
+
+ROOT = os.path.abspath(os.path.dirname(__file__))
+
+
+def test_translate_attrs_for_nautobot_default(nautobot_api_base):
+
+    prefix = NautobotPrefix(
+        diffsync=nautobot_api_base,
+        prefix="10.1.111.0/24",
+        site_name="HQ",
+        remote_id="014b6519-bf43-40f7-a40f-d5abb9828cd2",
+    )
+
+    expected_nb_params = {"prefix": "10.1.111.0/24", "status": "active", "site": "a325e477-62fe-47f0-8b67-acf411b1868f"}
+    nb_params = prefix.translate_attrs_for_nautobot(attrs={})
+
+    assert nb_params == expected_nb_params
+
+
+def test_translate_attrs_for_nautobot_with_vlan(nautobot_api_base):
+
+    prefix = NautobotPrefix(
+        diffsync=nautobot_api_base,
+        prefix="10.1.111.0/24",
+        site_name="HQ",
+        remote_id="014b6519-bf43-40f7-a40f-d5abb9828cd2",
+    )
+
+    expected_nb_params = {
+        "prefix": "10.1.111.0/24",
+        "status": "active",
+        "site": "a325e477-62fe-47f0-8b67-acf411b1868f",
+        "vlan": "464a2de3-fd5e-4b65-a58d-e0a2a617c12e",
+    }
+    nb_params = prefix.translate_attrs_for_nautobot(attrs=dict(vlan="HQ__111"))
+
+    assert nb_params == expected_nb_params
+
+
+def test_translate_attrs_for_nautobot_with_absent_vlan(nautobot_api_base):
+
+    prefix = NautobotPrefix(
+        diffsync=nautobot_api_base,
+        prefix="10.1.111.0/24",
+        site_name="HQ",
+        remote_id="014b6519-bf43-40f7-a40f-d5abb9828cd2",
+    )
+
+    expected_nb_params = {"prefix": "10.1.111.0/24", "status": "active", "site": "a325e477-62fe-47f0-8b67-acf411b1868f"}
+    nb_params = prefix.translate_attrs_for_nautobot(attrs=dict(vlan="HQ__112"))
+
+    assert nb_params == expected_nb_params
+
+
+def test_create_prefix(requests_mock, nautobot_api_base):
+
+    data = yaml.safe_load(open(f"{ROOT}/../fixtures/prefix_no_vlan.json"))
+
+    requests_mock.post("http://mock_nautobot/api/ipam/prefixes/", json=data, status_code=201)
+    ip_address = NautobotPrefix.create(
+        diffsync=nautobot_api_base, ids=dict(prefix="10.1.111.0/24", site_name="HQ"), attrs={}
+    )
+
+    assert isinstance(ip_address, NautobotPrefix) is True
+    assert ip_address.remote_id == "3e1d0bf7-43de-493e-901e-661647aac2af"
+    assert ip_address.vlan is None
+
+
+def test_update_prefix(requests_mock, nautobot_api_base):
+
+    data_no_vlan = yaml.safe_load(open(f"{ROOT}/../fixtures/prefix_no_vlan.json"))
+    data_vlan = yaml.safe_load(open(f"{ROOT}/../fixtures/prefix_vlan.json"))
+
+    remote_id = data_no_vlan["id"]
+
+    requests_mock.get(f"http://mock_nautobot/api/ipam/prefixes/{remote_id}/", json=data_no_vlan, status_code=200)
+    requests_mock.patch(f"http://mock_nautobot/api/ipam/prefixes/{remote_id}/", json=data_vlan, status_code=200)
+    prefix = NautobotPrefix(diffsync=nautobot_api_base, prefix="10.1.111.0/24", site_name="HQ", remote_id=remote_id)
+
+    prefix.update(attrs=dict(vlan="HQ__111"))
+    assert prefix.vlan == "HQ__111"
+
+
+def test_create_prefix_with_vlan(requests_mock, nautobot_api_base):
+
+    data = yaml.safe_load(open(f"{ROOT}/../fixtures/prefix_vlan.json"))
+
+    requests_mock.post("http://mock_nautobot/api/ipam/prefixes/", json=data, status_code=201)
+    prefix = NautobotPrefix.create(
+        diffsync=nautobot_api_base, ids=dict(prefix="10.1.111.0/24", site_name="HQ"), attrs=dict(vlan="HQ__111")
+    )
+
+    assert isinstance(prefix, NautobotPrefix) is True
+    assert prefix.remote_id == "71d0b310-f32b-4cc0-b940-afb8ab50b985"
+    assert prefix.vlan == "HQ__111"
+
+
+def test_translate_attrs_for_nautobot_w_vlan(nautobot_api_base):
+
+    prefix = NautobotPrefix(
+        diffsync=nautobot_api_base,
+        prefix="10.1.111.0/24",
+        site_name="HQ",
+        remote_id="171f82fc-8ab8-4840-9ae3-8337821be2fb",
+    )
+    nautobot_api_base.add(prefix)
+
+    params = prefix.translate_attrs_for_nautobot({"vlan": "HQ__111"})
+
+    assert "prefix" in params
+    assert params["site"] == "a325e477-62fe-47f0-8b67-acf411b1868f"
+    assert params["vlan"] == "464a2de3-fd5e-4b65-a58d-e0a2a617c12e"
+
+
+def test_translate_attrs_for_nautobot_wo_vlan(nautobot_api_base):
+
+    prefix = NautobotPrefix(
+        diffsync=nautobot_api_base,
+        prefix="10.1.111.0/24",
+        site_name="HQ",
+        remote_id="171f82fc-8ab8-4840-9ae3-8337821be2fb",
+    )
+    nautobot_api_base.add(prefix)
+
+    params = prefix.translate_attrs_for_nautobot({"vlan": None})
+
+    assert "prefix" in params
+    assert params["site"] == "a325e477-62fe-47f0-8b67-acf411b1868f"
+    assert "vlan" not in params
diff --git a/tests/unit/adapters/nautobot_api/models/test_nautobot_vlan.py b/tests/unit/adapters/nautobot_api/models/test_nautobot_vlan.py
new file mode 100644
index 00000000..35fc319f
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/models/test_nautobot_vlan.py
@@ -0,0 +1,215 @@
+"""test for NautobotVlan model."""
+import os
+
+import pynautobot
+import pytest
+import yaml
+from diffsync.exceptions import ObjectNotFound
+from network_importer.adapters.nautobot_api.models import NautobotDevice, NautobotVlan
+
+ROOT = os.path.abspath(os.path.dirname(__file__))
+
+
+def test_vlan_create_from_pynautobot(nautobot_api_base):
+    api = pynautobot.api(url="http://mock_nautobot", token="1234567890")
+
+    data = yaml.safe_load(open(f"{ROOT}/../fixtures/vlan_101_no_tag.json"))
+    pnb = pynautobot.core.response.Record(values=data, api=api, endpoint="eb697742-364d-4714-b585-a267c64d7720")
+
+    item = NautobotVlan.create_from_pynautobot(diffsync=nautobot_api_base, obj=pnb, site_name="nyc")
+    assert isinstance(item, NautobotVlan) is True
+    assert item.remote_id == "eb697742-364d-4714-b585-a267c64d7720"
+    assert item.vid == 101
+    assert item.associated_devices == []
+
+
+def test_vlan_create_from_pynautobot_with_tags(nautobot_api_base):
+
+    api = pynautobot.api(url="http://mock_nautobot", token="1234567890")
+
+    data = yaml.safe_load(open(f"{ROOT}/../fixtures/vlan_101_tags_01.json"))
+    pnb = pynautobot.core.response.Record(values=data, api=api, endpoint=1)
+
+    nautobot_api_base.add(
+        NautobotDevice(name="devA", site_name="nyc", remote_id="eb697742-364d-4714-b585-a267c64d7720")
+    )
+
+    item = NautobotVlan.create_from_pynautobot(diffsync=nautobot_api_base, obj=pnb, site_name="nyc")
+    assert isinstance(item, NautobotVlan) is True
+    assert item.remote_id == "eb697742-364d-4714-b585-a267c64d7720"
+    assert item.vid == 101
+    assert item.associated_devices == ["devA"]
+
+    # Try again with one additional device in the inventory
+    nautobot_api_base.add(
+        NautobotDevice(name="devB", site_name="nyc", remote_id="eb697742-364d-4714-b585-a267c64d7731")
+    )
+    item = NautobotVlan.create_from_pynautobot(diffsync=nautobot_api_base, obj=pnb, site_name="nyc")
+    assert isinstance(item, NautobotVlan) is True
+    assert item.remote_id == "eb697742-364d-4714-b585-a267c64d7720"
+    assert item.vid == 101
+    assert item.associated_devices == ["devA", "devB"]
+
+
+def test_translate_attrs_for_nautobot_no_attrs(nautobot_api_base):
+
+    vlan = NautobotVlan(vid=100, site_name="HQ", remote_id="eb697742-364d-4714-b585-a267c64d7720")
+    nautobot_api_base.add(vlan)
+
+    params = vlan.translate_attrs_for_nautobot({})
+
+    assert "name" in params
+    assert params["name"] == "vlan-100"
+    assert "site" in params
+    assert params["site"] == "a325e477-62fe-47f0-8b67-acf411b1868f"
+    assert "tags" not in params
+
+
+def test_translate_attrs_for_nautobot_with_partial_attrs(nautobot_api_base):
+
+    vlan = NautobotVlan(vid=100, name="MYVLAN", site_name="HQ", remote_id="464a2de3-fd5e-4b65-a58d-e0a2a617c12e")
+    nautobot_api_base.add(vlan)
+
+    nautobot_api_base.add(
+        NautobotDevice(
+            name="dev1",
+            site_name="HQ",
+            remote_id="e0633a07-c3e2-41b0-a1df-4627392acf0a",
+            device_tag_id="0bc28fc5-4e3d-4e84-b407-318c2151d64e",
+        )
+    )
+    nautobot_api_base.add(
+        NautobotDevice(
+            name="dev2",
+            site_name="HQ",
+            remote_id="e0633a07-c3e2-41b0-a1df-4627392acf0b",
+            device_tag_id="0bc28fc5-4e3d-4e84-b407-318c2151d65a",
+        )
+    )
+    params = vlan.translate_attrs_for_nautobot({"associated_devices": ["dev1", "dev2"]})
+
+    assert "name" not in params
+    assert "site" in params
+    assert params["site"] == "a325e477-62fe-47f0-8b67-acf411b1868f"
+    assert "tags" in params
+    assert sorted(params["tags"]) == ["0bc28fc5-4e3d-4e84-b407-318c2151d64e", "0bc28fc5-4e3d-4e84-b407-318c2151d65a"]
+
+
+def test_translate_attrs_for_nautobot_with_attrs(nautobot_api_base):
+
+    vlan = NautobotVlan(vid=100, site_name="HQ", remote_id="464a2de3-fd5e-4b65-a58d-e0a2a617c12e")
+    nautobot_api_base.add(vlan)
+
+    nautobot_api_base.add(
+        NautobotDevice(
+            name="dev1",
+            site_name="HQ",
+            remote_id="e0633a07-c3e2-41b0-a1df-4627392acf0a",
+            device_tag_id="0bc28fc5-4e3d-4e84-b407-318c2151d64e",
+        )
+    )
+    nautobot_api_base.add(
+        NautobotDevice(
+            name="dev2",
+            site_name="HQ",
+            remote_id="e0633a07-c3e2-41b0-a1df-4627392acf0b",
+            device_tag_id="0bc28fc5-4e3d-4e84-b407-318c2151d65a",
+        )
+    )
+    params = vlan.translate_attrs_for_nautobot({"name": "VOICE", "associated_devices": ["dev1", "dev2"]})
+
+    assert "name" in params
+    assert params["name"] == "VOICE"
+    assert "site" in params
+    assert params["site"] == "a325e477-62fe-47f0-8b67-acf411b1868f"
+    assert "tags" in params
+    assert sorted(params["tags"]) == ["0bc28fc5-4e3d-4e84-b407-318c2151d64e", "0bc28fc5-4e3d-4e84-b407-318c2151d65a"]
+
+
+def test_translate_attrs_for_nautobot_with_missing_devices(nautobot_api_base):
+
+    vlan = NautobotVlan(vid=100, site_name="HQ", remote_id="464a2de3-fd5e-4b65-a58d-e0a2a617c12e")
+    nautobot_api_base.add(vlan)
+
+    nautobot_api_base.add(
+        NautobotDevice(
+            name="dev1",
+            site_name="HQ",
+            remote_id="e0633a07-c3e2-41b0-a1df-4627392acf0a",
+            device_tag_id="0bc28fc5-4e3d-4e84-b407-318c2151d64e",
+        )
+    )
+    params = vlan.translate_attrs_for_nautobot({"name": "VOICE", "associated_devices": ["dev1", "dev2"]})
+
+    assert "name" in params
+    assert params["name"] == "VOICE"
+    assert "site" in params
+    assert params["site"] == "a325e477-62fe-47f0-8b67-acf411b1868f"
+    assert "tags" in params
+    assert sorted(params["tags"]) == ["0bc28fc5-4e3d-4e84-b407-318c2151d64e"]
+
+
+def test_translate_attrs_for_nautobot_missing_site(nautobot_api_base):
+
+    vlan = NautobotVlan(vid=100, site_name="NOTPRESENT", remote_id="464a2de3-fd5e-4b65-a58d-e0a2a617c12e")
+    nautobot_api_base.add(vlan)
+
+    with pytest.raises(ObjectNotFound):
+        vlan.translate_attrs_for_nautobot({})
+        assert True
+
+
+def test_update_clean_tags_no_incoming_tags(nautobot_api_base):
+
+    vlan = NautobotVlan(vid=100, site_name="HQ", remote_id="464a2de3-fd5e-4b65-a58d-e0a2a617c12e")
+    nautobot_api_base.add(vlan)
+
+    api = pynautobot.api(url="http://mock_nautobot", token="1234567890")
+    data = yaml.safe_load(open(f"{ROOT}/../fixtures/vlan_101_tags_01.json"))
+    pnb = pynautobot.core.response.Record(values=data, api=api, endpoint="eb697742-364d-4714-b585-a267c64d7720")
+
+    params = vlan.translate_attrs_for_nautobot({"name": "VOICE"})
+
+    clean_params = vlan.update_clean_tags(nb_params=params, obj=pnb)
+
+    assert "tags" not in clean_params
+
+
+def test_update_clean_tags_with_incoming_tags(nautobot_api_base):
+
+    vlan = NautobotVlan(vid=100, site_name="HQ", remote_id="464a2de3-fd5e-4b65-a58d-e0a2a617c12e")
+    nautobot_api_base.add(vlan)
+
+    nautobot_api_base.add(
+        NautobotDevice(
+            name="dev1",
+            site_name="HQ",
+            remote_id="e0633a07-c3e2-41b0-a1df-4627392acf0a",
+            device_tag_id="0bc28fc5-4e3d-4e84-b407-318c2151d64e",
+        )
+    )
+    nautobot_api_base.add(
+        NautobotDevice(
+            name="dev2",
+            site_name="HQ",
+            remote_id="e0633a07-c3e2-41b0-a1df-4627392acf0b",
+            device_tag_id="0bc28fc5-4e3d-4e84-b407-318c2151d65a",
+        )
+    )
+
+    api = pynautobot.api(url="http://mock_nautobot", token="1234567890")
+    data = yaml.safe_load(open(f"{ROOT}/../fixtures/vlan_101_tags_01.json"))
+    pnb = pynautobot.core.response.Record(values=data, api=api, endpoint="eb697742-364d-4714-b585-a267c64d7720")
+
+    params = vlan.translate_attrs_for_nautobot({"name": "VOICE", "associated_devices": ["dev1", "dev2"]})
+    clean_params = vlan.update_clean_tags(nb_params=params, obj=pnb)
+
+    assert "tags" in clean_params
+    print(clean_params)
+    assert sorted(clean_params["tags"]) == [
+        "0bc28fc5-4e3d-4e84-b407-318c2151d64e",
+        "0bc28fc5-4e3d-4e84-b407-318c2151d65a",
+        "999121c7-37d6-44a8-83f9-61706e915bde",
+        "d0c52a6c-b3e9-4234-98ef-ee9b76ca31db",
+        "fd6809fa-26cf-47b2-8742-974cd4d22ca9",
+    ]
diff --git a/tests/unit/adapters/nautobot_api/test_nautobot_inventory.py b/tests/unit/adapters/nautobot_api/test_nautobot_inventory.py
new file mode 100644
index 00000000..5e17e2af
--- /dev/null
+++ b/tests/unit/adapters/nautobot_api/test_nautobot_inventory.py
@@ -0,0 +1,160 @@
+"""test for NautobotAPI Inventory."""
+# pylint: disable=E1101
+
+from os import path
+import yaml
+from network_importer.adapters.nautobot_api.inventory import NautobotAPIInventory
+
+HERE = path.abspath(path.dirname(__file__))
+FIXTURES = "fixtures/inventory"
+
+
+def test_nautobot_inventory_all(requests_mock):
+    """
+    Test nautobot dynamic inventory without filter parameters
+
+    Args:
+        requests_mock (:obj:`requests_mock.mocker.Mocker`): Automatically inserted
+        by pytest library, mocks requests get to external API so external API call is
+        not needed for unit test.
+    """
+    # Load mock data fixtures
+    data1 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/devices.json"))
+    requests_mock.get("http://mock/api/dcim/devices/?exclude=config_context", json=data1)
+
+    data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json"))
+    requests_mock.get("http://mock/api/dcim/platforms/", json=data2)
+
+    inv = NautobotAPIInventory(settings=dict(address="http://mock", token="12349askdnfanasdf"),).load()  # nosec
+
+    assert len(inv.hosts.keys()) == 3
+    assert "grb-rtr01" in inv.hosts.keys()
+    assert inv.hosts["grb-rtr01"].platform == "ios"
+    assert "msp-rtr01" in inv.hosts.keys()
+    assert inv.hosts["msp-rtr01"].platform == "nxos"
+    assert inv.hosts["msp-rtr01"].connection_options["napalm"].platform == "nxos_napalm"
+    assert "sw01" in inv.hosts.keys()
+    assert inv.hosts["sw01"].platform == "cisco_ios"
+    assert inv.hosts["sw01"].connection_options["napalm"].platform == "ios"
+
+
+def test_nb_inventory_filtered(requests_mock):
+    """
+    Test nautobot dynamic inventory with filter parameters
+
+    Args:
+        requests_mock (:obj:`requests_mock.mocker.Mocker`): Automatically inserted
+        by pytest library, mocks requests get to external API so external API call is
+        not needed for unit test.
+    """
+    # Load mock data fixtures
+    data1 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/filtered_devices.json"))
+    requests_mock.get("http://mock/api/dcim/devices/?name=grb-rtr01&exclude=config_context", json=data1)
+
+    data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json"))
+    requests_mock.get("http://mock/api/dcim/platforms/", json=data2)
+
+    inv_filtered = NautobotAPIInventory(
+        limit="grb-rtr01", settings=dict(address="http://mock", token="12349askdnfanasdf",),  # nosec
+    ).load()  # nosec
+
+    assert len(inv_filtered.hosts.keys()) == 1
+    assert "grb-rtr01" in inv_filtered.hosts.keys()
+    assert "msp-rtr01" not in inv_filtered.hosts.keys()
+
+
+def test_nb_inventory_exclude(requests_mock):
+    """
+    Test nautobot dynamic inventory with exclude filter parameters
+
+    Args:
+        requests_mock (:obj:`requests_mock.mocker.Mocker`): Automatically inserted
+        by pytest library, mocks requests get to external API so external API call is
+        not needed for unit test.
+    """
+    # Load mock data fixtures
+    data1 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/devices.json"))
+    requests_mock.get("http://mock/api/dcim/devices/?exclude=platform", json=data1)
+
+    data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json"))
+    requests_mock.get("http://mock/api/dcim/platforms/", json=data2)
+
+    inv = NautobotAPIInventory(
+        settings=dict(address="http://mock", token="12349askdnfanasdf", filter="exclude=platform",),  # nosec  # nosec
+    ).load()  # nosec
+
+    assert len(inv.hosts.keys()) == 3
+
+
+def test_nb_inventory_virtual_chassis(requests_mock):
+    """
+    Test nautobot virtual_chassis attribute set correctly
+
+    Args:
+        requests_mock (:obj:`requests_mock.mocker.Mocker`): Automatically inserted
+        by pytest library, mocks requests get to external API so external API call is
+        not needed for unit test.
+    """
+    # Load mock data fixtures
+    data1 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/stack_devices.json"))
+    requests_mock.get("http://mock/api/dcim/devices/", json=data1)
+
+    data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json"))
+    requests_mock.get("http://mock/api/dcim/platforms/", json=data2)
+
+    inv = NautobotAPIInventory(
+        username="mock",
+        password="mock",
+        enable="mock",
+        supported_platforms=["cisco_ios", "nxos", "ios"],
+        limit=None,
+        settings=dict(address="http://mock", token="12349askdnfanasdf"),
+    ).load()  # nosec
+
+    assert len(inv.hosts.keys()) == 4
+    assert "stack01:2" not in inv.hosts.keys()
+    assert "stack01" in inv.hosts.keys()
+    assert inv.hosts["stack01"].data["virtual_chassis"]
+    assert not inv.hosts["msp-rtr01"].data["virtual_chassis"]
+
+
+def test_nb_inventory_supported_platforms(requests_mock):
+    """
+    Test nautobot dynamic inventory with a list of supported platforms
+
+    Args:
+        requests_mock (:obj:`requests_mock.mocker.Mocker`): Automatically inserted
+        by pytest library, mocks requests get to external API so external API call is
+        not needed for unit test.
+    """
+    # Load mock data fixtures
+    data1 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/devices.json"))
+    requests_mock.get("http://mock/api/dcim/devices/", json=data1)
+
+    data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json"))
+    requests_mock.get("http://mock/api/dcim/platforms/", json=data2)
+
+    inv = NautobotAPIInventory(
+        supported_platforms=["ios", "nxos"],
+        settings=dict(address="http://mock", token="12349askdnfanasdf")  # nosec  # nosec
+        # nosec
+    ).load()  # nosec
+
+    assert len(inv.hosts.keys()) == 2
+    assert "grb-rtr01" in inv.hosts.keys()
+    assert "msp-rtr01" in inv.hosts.keys()
+    assert "sw01" not in inv.hosts.keys()
+
+    inv = NautobotAPIInventory(  # nosec
+        username="mock",
+        password="mock",
+        enable="mock",
+        limit=None,
+        supported_platforms=["ios"],
+        settings=dict(address="http://mock", token="12349askdnfanasdf"),  # nosec
+    ).load()  # nosec
+
+    assert len(inv.hosts.keys()) == 1
+    assert "grb-rtr01" in inv.hosts.keys()
+    assert "msp-rtr01" not in inv.hosts.keys()
+    assert "sw01" not in inv.hosts.keys()
diff --git a/tests/unit/adapters/netbox_api/fixtures/inventory/devices.json b/tests/unit/adapters/netbox_api/fixtures/inventory/devices.json
new file mode 100644
index 00000000..1f614f0d
--- /dev/null
+++ b/tests/unit/adapters/netbox_api/fixtures/inventory/devices.json
@@ -0,0 +1,364 @@
+{
+  "count": 6,
+  "next": null,
+  "previous": null,
+  "results": [
+    {
+      "id": 7,
+      "name": "amarillo",
+      "display_name": "amarillo",
+      "device_type": {
+        "id": 1,
+        "url": "http://localhost/api/dcim/device-types/1/",
+        "manufacturer": {
+          "id": 1,
+          "url": "http://localhost/api/dcim/manufacturers/1/",
+          "name": "juniper",
+          "slug": "juniper"
+        },
+        "model": "junos",
+        "slug": "junos",
+        "display_name": "juniper junos"
+      },
+      "device_role": {
+        "id": 1,
+        "url": "http://localhost/api/dcim/device-roles/1/",
+        "name": "spine",
+        "slug": "spine"
+      },
+      "tenant": null,
+      "platform": null,
+      "serial": "",
+      "asset_tag": null,
+      "site": {
+        "id": 1,
+        "url": "http://localhost/api/dcim/sites/1/",
+        "name": "ni_example_01",
+        "slug": "ni_example_01"
+      },
+      "rack": null,
+      "position": null,
+      "face": null,
+      "parent_device": null,
+      "status": {
+        "value": "active",
+        "label": "Active",
+        "id": 1
+      },
+      "primary_ip": null,
+      "primary_ip4": null,
+      "primary_ip6": null,
+      "cluster": null,
+      "virtual_chassis": null,
+      "vc_position": null,
+      "vc_priority": null,
+      "comments": "",
+      "local_context_data": null,
+      "tags": [],
+      "custom_fields": {},
+      "config_context": {},
+      "created": "2020-02-10",
+      "last_updated": "2020-02-11T22:35:16.062047Z"
+    },
+    {
+      "id": 13,
+      "name": "austin",
+      "display_name": "austin",
+      "device_type": {
+        "id": 2,
+        "url": "http://localhost/api/dcim/device-types/2/",
+        "manufacturer": {
+          "id": 2,
+          "url": "http://localhost/api/dcim/manufacturers/2/",
+          "name": "cisco",
+          "slug": "cisco"
+        },
+        "model": "iosxr",
+        "slug": "iosxr",
+        "display_name": "cisco iosxr"
+      },
+      "device_role": {
+        "id": 1,
+        "url": "http://localhost/api/dcim/device-roles/1/",
+        "name": "spine",
+        "slug": "spine"
+      },
+      "tenant": null,
+      "platform": {
+        "id": 2,
+        "url": "http://localhost/api/dcim/platforms/2/",
+        "name": "ios",
+        "slug": "ios"
+      },
+      "serial": "",
+      "asset_tag": null,
+      "site": {
+        "id": 1,
+        "url": "http://localhost/api/dcim/sites/1/",
+        "name": "ni_example_01",
+        "slug": "ni_example_01"
+      },
+      "rack": null,
+      "position": null,
+      "face": null,
+      "parent_device": null,
+      "status": {
+        "value": "active",
+        "label": "Active",
+        "id": 1
+      },
+      "primary_ip": null,
+      "primary_ip4": null,
+      "primary_ip6": null,
+      "cluster": null,
+      "virtual_chassis": null,
+      "vc_position": null,
+      "vc_priority": null,
+      "comments": "",
+      "local_context_data": null,
+      "tags": [],
+      "custom_fields": {},
+      "config_context": {},
+      "created": "2020-02-11",
+      "last_updated": "2020-02-11T00:03:10.763181Z"
+    },
+    {
+      "id": 16,
+      "name": "dallas",
+      "display_name": "dallas",
+      "device_type": {
+        "id": 2,
+        "url": "http://localhost/api/dcim/device-types/2/",
+        "manufacturer": {
+          "id": 2,
+          "url": "http://localhost/api/dcim/manufacturers/2/",
+          "name": "cisco",
+          "slug": "cisco"
+        },
+        "model": "iosxr",
+        "slug": "iosxr",
+        "display_name": "cisco iosxr"
+      },
+      "device_role": {
+        "id": 2,
+        "url": "http://localhost/api/dcim/device-roles/2/",
+        "name": "leaf",
+        "slug": "leaf"
+      },
+      "tenant": null,
+      "platform": {
+        "id": 3,
+        "url": "http://localhost/api/dcim/platforms/3/",
+        "name": "nxos",
+        "slug": "nxos"
+      },
+      "serial": "",
+      "asset_tag": null,
+      "site": {
+        "id": 1,
+        "url": "http://localhost/api/dcim/sites/1/",
+        "name": "ni_example_01",
+        "slug": "ni_example_01"
+      },
+      "rack": null,
+      "position": null,
+      "face": null,
+      "parent_device": null,
+      "status": {
+        "value": "active",
+        "label": "Active",
+        "id": 1
+      },
+      "primary_ip": null,
+      "primary_ip4": null,
+      "primary_ip6": null,
+      "cluster": null,
+      "virtual_chassis": null,
+      "vc_position": null,
+      "vc_priority": null,
+      "comments": "",
+      "local_context_data": null,
+      "tags": [],
+      "custom_fields": {},
+      "config_context": {},
+      "created": "2020-02-11",
+      "last_updated": "2020-02-11T00:03:11.077543Z"
+    },
+    {
+      "id": 14,
+      "name": "el-paso",
+      "display_name": "el-paso",
+      "device_type": {
+        "id": 2,
+        "url": "http://localhost/api/dcim/device-types/2/",
+        "manufacturer": {
+          "id": 2,
+          "url": "http://localhost/api/dcim/manufacturers/2/",
+          "name": "cisco",
+          "slug": "cisco"
+        },
+        "model": "iosxr",
+        "slug": "iosxr",
+        "display_name": "cisco iosxr"
+      },
+      "device_role": {
+        "id": 1,
+        "url": "http://localhost/api/dcim/device-roles/1/",
+        "name": "spine",
+        "slug": "spine"
+      },
+      "tenant": null,
+      "platform": {
+        "id": 4,
+        "url": "http://localhost/api/dcim/platforms/4/",
+        "name": "asa",
+        "slug": "asa"
+      },
+      "serial": "",
+      "asset_tag": null,
+      "site": {
+        "id": 1,
+        "url": "http://localhost/api/dcim/sites/1/",
+        "name": "ni_example_01",
+        "slug": "ni_example_01"
+      },
+      "rack": null,
+      "position": null,
+      "face": null,
+      "parent_device": null,
+      "status": {
+        "value": "active",
+        "label": "Active",
+        "id": 1
+      },
+      "primary_ip": null,
+      "primary_ip4": null,
+      "primary_ip6": null,
+      "cluster": null,
+      "virtual_chassis": null,
+      "vc_position": null,
+      "vc_priority": null,
+      "comments": "",
+      "local_context_data": null,
+      "tags": [],
+      "custom_fields": {},
+      "config_context": {},
+      "created": "2020-02-11",
+      "last_updated": "2020-02-11T00:03:10.768410Z"
+    },
+    {
+      "id": 17,
+      "name": "houston",
+      "display_name": "houston",
+      "device_type": {
+        "id": 3,
+        "url": "http://localhost/api/dcim/device-types/3/",
+        "manufacturer": {
+          "id": 3,
+          "url": "http://localhost/api/dcim/manufacturers/3/",
+          "name": "arista",
+          "slug": "arista"
+        },
+        "model": "eos",
+        "slug": "eos",
+        "display_name": "arista eos"
+      },
+      "device_role": {
+        "id": 2,
+        "url": "http://localhost/api/dcim/device-roles/2/",
+        "name": "leaf",
+        "slug": "leaf"
+      },
+      "tenant": null,
+      "platform": null,
+      "serial": "",
+      "asset_tag": null,
+      "site": {
+        "id": 1,
+        "url": "http://localhost/api/dcim/sites/1/",
+        "name": "ni_example_01",
+        "slug": "ni_example_01"
+      },
+      "rack": null,
+      "position": null,
+      "face": null,
+      "parent_device": null,
+      "status": {
+        "value": "active",
+        "label": "Active",
+        "id": 1
+      },
+      "primary_ip": null,
+      "primary_ip4": null,
+      "primary_ip6": null,
+      "cluster": null,
+      "virtual_chassis": null,
+      "vc_position": null,
+      "vc_priority": null,
+      "comments": "",
+      "local_context_data": null,
+      "tags": [],
+      "custom_fields": {},
+      "config_context": {},
+      "created": "2020-02-11",
+      "last_updated": "2020-02-11T00:03:12.036015Z"
+    },
+    {
+      "id": 15,
+      "name": "san-antonio",
+      "display_name": "san-antonio",
+      "device_type": {
+        "id": 2,
+        "url": "http://localhost/api/dcim/device-types/2/",
+        "manufacturer": {
+          "id": 2,
+          "url": "http://localhost/api/dcim/manufacturers/2/",
+          "name": "cisco",
+          "slug": "cisco"
+        },
+        "model": "iosxr",
+        "slug": "iosxr",
+        "display_name": "cisco iosxr"
+      },
+      "device_role": {
+        "id": 1,
+        "url": "http://localhost/api/dcim/device-roles/1/",
+        "name": "spine",
+        "slug": "spine"
+      },
+      "tenant": null,
+      "platform": null,
+      "serial": "",
+      "asset_tag": null,
+      "site": {
+        "id": 1,
+        "url": "http://localhost/api/dcim/sites/1/",
+        "name": "ni_example_01",
+        "slug": "ni_example_01"
+      },
+      "rack": null,
+      "position": null,
+      "face": null,
+      "parent_device": null,
+      "status": {
+        "value": "active",
+        "label": "Active",
+        "id": 1
+      },
+      "primary_ip": null,
+      "primary_ip4": null,
+      "primary_ip6": null,
+      "cluster": null,
+      "virtual_chassis": null,
+      "vc_position": null,
+      "vc_priority": null,
+      "comments": "",
+      "local_context_data": null,
+      "tags": [],
+      "custom_fields": {},
+      "config_context": {},
+      "created": "2020-02-11",
+      "last_updated": "2020-02-11T00:03:11.055575Z"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/unit/adapters/netbox_api/fixtures/inventory/filtered_devices.json b/tests/unit/adapters/netbox_api/fixtures/inventory/filtered_devices.json
new file mode 100644
index 00000000..c981ba8a
--- /dev/null
+++ b/tests/unit/adapters/netbox_api/fixtures/inventory/filtered_devices.json
@@ -0,0 +1,64 @@
+{
+    "count": 6,
+    "next": null,
+    "previous": null,
+    "results": [
+        {
+            "id": 14,
+            "name": "el-paso",
+            "display_name": "el-paso",
+            "device_type": {
+                "id": 2,
+                "url": "http://localhost/api/dcim/device-types/2/",
+                "manufacturer": {
+                    "id": 2,
+                    "url": "http://localhost/api/dcim/manufacturers/2/",
+                    "name": "cisco",
+                    "slug": "cisco"
+                },
+                "model": "iosxr",
+                "slug": "iosxr",
+                "display_name": "cisco iosxr"
+            },
+            "device_role": {
+                "id": 1,
+                "url": "http://localhost/api/dcim/device-roles/1/",
+                "name": "spine",
+                "slug": "spine"
+            },
+            "tenant": null,
+            "platform": null,
+            "serial": "",
+            "asset_tag": null,
+            "site": {
+                "id": 1,
+                "url": "http://localhost/api/dcim/sites/1/",
+                "name": "ni_example_01",
+                "slug": "ni_example_01"
+            },
+            "rack": null,
+            "position": null,
+            "face": null,
+            "parent_device": null,
+            "status": {
+                "value": "active",
+                "label": "Active",
+                "id": 1
+            },
+            "primary_ip": null,
+            "primary_ip4": null,
+            "primary_ip6": null,
+            "cluster": null,
+            "virtual_chassis": null,
+            "vc_position": null,
+            "vc_priority": null,
+            "comments": "",
+            "local_context_data": null,
+            "tags": [],
+            "custom_fields": {},
+            "config_context": {},
+            "created": "2020-02-11",
+            "last_updated": "2020-02-11T00:03:10.768410Z"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/tests/unit/adapters/netbox_api/fixtures/inventory/platforms.json b/tests/unit/adapters/netbox_api/fixtures/inventory/platforms.json
new file mode 100644
index 00000000..b156046c
--- /dev/null
+++ b/tests/unit/adapters/netbox_api/fixtures/inventory/platforms.json
@@ -0,0 +1,52 @@
+{
+    "count": 3,
+    "next": null,
+    "previous": null,
+    "results": [
+      {
+        "id": 4,
+        "name": "cisco_asa",
+        "slug": "cisco_asa",
+        "manufacturer": {
+          "id": 1,
+          "url": "http://localhost/api/dcim/manufacturers/1/",
+          "name": "Cisco",
+          "slug": "cisco"
+        },
+        "napalm_driver": "",
+        "napalm_args": null,
+        "device_count": 0,
+        "virtualmachine_count": null
+      },
+      {
+        "id": 2,
+        "name": "ios",
+        "slug": "ios",
+        "manufacturer": {
+          "id": 1,
+          "url": "http://localhost/api/dcim/manufacturers/1/",
+          "name": "Cisco",
+          "slug": "cisco"
+        },
+        "napalm_driver": "ios_naplam",
+        "napalm_args": null,
+        "device_count": 0,
+        "virtualmachine_count": null
+      },
+      {
+        "id": 3,
+        "name": "nxos",
+        "slug": "nxos",
+        "manufacturer": {
+          "id": 1,
+          "url": "http://localhost/api/dcim/manufacturers/1/",
+          "name": "Cisco",
+          "slug": "cisco"
+        },
+        "napalm_driver": "nxos_naplam",
+        "napalm_args": null,
+        "device_count": 0,
+        "virtualmachine_count": null
+      }
+    ]
+  }
\ No newline at end of file
diff --git a/tests/unit/adapters/netbox_api/fixtures/inventory/stack_devices.json b/tests/unit/adapters/netbox_api/fixtures/inventory/stack_devices.json
new file mode 100644
index 00000000..8eb08a4c
--- /dev/null
+++ b/tests/unit/adapters/netbox_api/fixtures/inventory/stack_devices.json
@@ -0,0 +1,217 @@
+{
+  "count": 1,
+  "next": null,
+  "previous": null,
+  "results": [
+    {
+      "id": 5,
+      "name": "test_dev1_2",
+      "display_name": "test_dev1_2",
+      "device_type": {
+        "id": 1,
+        "url": "http://localhost:32768/api/dcim/device-types/1/",
+        "manufacturer": {
+          "id": 1,
+          "url": "http://localhost:32768/api/dcim/manufacturers/1/",
+          "name": "cisco",
+          "slug": "cisco"
+        },
+        "model": "ios",
+        "slug": "ios",
+        "display_name": "cisco ios"
+      },
+      "device_role": {
+        "id": 1,
+        "url": "http://localhost:32768/api/dcim/device-roles/1/",
+        "name": "access",
+        "slug": "access"
+      },
+      "tenant": null,
+      "platform": {
+        "id": 1,
+        "url": "http://localhost:32768/api/dcim/platforms/1/",
+        "name": "cisco_ios",
+        "slug": "cisco_ios"
+      },
+      "serial": "",
+      "asset_tag": null,
+      "site": {
+        "id": 1,
+        "url": "http://localhost:32768/api/dcim/sites/1/",
+        "name": "test_site",
+        "slug": "test_site"
+      },
+      "rack": null,
+      "position": null,
+      "face": null,
+      "parent_device": null,
+      "status": {
+        "value": 1,
+        "label": "Active"
+      },
+      "primary_ip": null,
+      "primary_ip4": null,
+      "primary_ip6": null,
+      "cluster": null,
+      "virtual_chassis": {
+        "id": 1,
+        "url": "http://localhost:32768/api/dcim/virtual-chassis/1/",
+        "master": {
+          "id": 4,
+          "url": "http://localhost:32768/api/dcim/devices/4/",
+          "name": "test_dev1",
+          "display_name": "test_dev1"
+        }
+      },
+      "vc_position": 2,
+      "vc_priority": 14,
+      "comments": "",
+      "local_context_data": null,
+      "tags": [],
+      "custom_fields": {},
+      "config_context": {},
+      "created": "2020-02-19",
+      "last_updated": "2020-02-19T19:35:45.568873Z"
+    },
+    {
+      "id": 4,
+      "name": "test_dev1",
+      "display_name": "test_dev1",
+      "device_type": {
+        "id": 1,
+        "url": "http://localhost:32768/api/dcim/device-types/1/",
+        "manufacturer": {
+          "id": 1,
+          "url": "http://localhost:32768/api/dcim/manufacturers/1/",
+          "name": "cisco",
+          "slug": "cisco"
+        },
+        "model": "ios",
+        "slug": "ios",
+        "display_name": "cisco ios"
+      },
+      "device_role": {
+        "id": 1,
+        "url": "http://localhost:32768/api/dcim/device-roles/1/",
+        "name": "access",
+        "slug": "access"
+      },
+      "tenant": null,
+      "platform": {
+        "id": 1,
+        "url": "http://localhost:32768/api/dcim/platforms/1/",
+        "name": "cisco_ios",
+        "slug": "cisco_ios"
+      },
+      "serial": "",
+      "asset_tag": null,
+      "site": {
+        "id": 1,
+        "url": "http://localhost:32768/api/dcim/sites/1/",
+        "name": "test_site",
+        "slug": "test_site"
+      },
+      "rack": null,
+      "position": null,
+      "face": null,
+      "parent_device": null,
+      "status": {
+        "value": 1,
+        "label": "Active"
+      },
+      "primary_ip": {
+        "id": 3,
+        "url": "http://localhost:32768/api/ipam/ip-addresses/3/",
+        "family": 4,
+        "address": "10.50.3.2/32"
+      },
+      "primary_ip4": {
+        "id": 3,
+        "url": "http://localhost:32768/api/ipam/ip-addresses/3/",
+        "family": 4,
+        "address": "10.50.3.2/32"
+      },
+      "primary_ip6": null,
+      "cluster": null,
+      "virtual_chassis": {
+        "id": 1,
+        "url": "http://localhost:32768/api/dcim/virtual-chassis/1/",
+        "master": {
+          "id": 4,
+          "url": "http://localhost:32768/api/dcim/devices/4/",
+          "name": "test_dev1",
+          "display_name": "test_dev1"
+        }
+      },
+      "vc_position": 1,
+      "vc_priority": 15,
+      "comments": "",
+      "local_context_data": null,
+      "tags": [],
+      "custom_fields": {},
+      "config_context": {},
+      "created": "2020-02-19",
+      "last_updated": "2020-02-19T19:38:47.113568Z"
+    },
+    {
+        "id": 10,
+        "name": "amarillo",
+        "display_name": "amarillo",
+        "device_type": {
+          "id": 2,
+          "url": "http://localhost:32768/api/dcim/device-types/2/",
+          "manufacturer": {
+            "id": 3,
+            "url": "http://localhost:32768/api/dcim/manufacturers/3/",
+            "name": "juniper",
+            "slug": "juniper"
+          },
+          "model": "junos",
+          "slug": "junos",
+          "display_name": "juniper junos"
+        },
+        "device_role": {
+          "id": 2,
+          "url": "http://localhost:32768/api/dcim/device-roles/2/",
+          "name": "spine",
+          "slug": "spine"
+        },
+        "tenant": null,
+        "platform": null,
+        "serial": "",
+        "asset_tag": null,
+        "site": {
+          "id": 3,
+          "url": "http://localhost:32768/api/dcim/sites/3/",
+          "name": "ni_example_01",
+          "slug": "ni_example_01"
+        },
+        "rack": null,
+        "position": null,
+        "face": null,
+        "parent_device": null,
+        "status": {
+          "value": 1,
+          "label": "Active"
+        },
+        "primary_ip": null,
+        "primary_ip4": null,
+        "primary_ip6": null,
+        "cluster": null,
+        "virtual_chassis": {
+          "id": 1,
+          "url": "http://localhost:32768/api/dcim/virtual-chassis/1/",
+          "master": null
+        },
+        "vc_position": null,
+        "vc_priority": null,
+        "comments": "",
+        "local_context_data": null,
+        "tags": [],
+        "custom_fields": {},
+        "config_context": {},
+        "created": "2020-02-19",
+        "last_updated": "2020-02-19T19:31:54.203479Z"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/unit/adapters/netbox_api/models/test_netbox_device.py b/tests/unit/adapters/netbox_api/models/test_netbox_device.py
index 76bc08ce..02631cc4 100644
--- a/tests/unit/adapters/netbox_api/models/test_netbox_device.py
+++ b/tests/unit/adapters/netbox_api/models/test_netbox_device.py
@@ -1,16 +1,4 @@
-"""
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""test for NetboxDevice model."""
 import os
 import yaml
 
diff --git a/tests/unit/adapters/netbox_api/models/test_netbox_interface.py b/tests/unit/adapters/netbox_api/models/test_netbox_interface.py
index 67569a16..9f7bd159 100644
--- a/tests/unit/adapters/netbox_api/models/test_netbox_interface.py
+++ b/tests/unit/adapters/netbox_api/models/test_netbox_interface.py
@@ -1,16 +1,4 @@
-"""
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""test for NetboxInterface model."""
 import os
 import pytest
 
@@ -56,7 +44,7 @@ def test_translate_attrs_for_netbox_device_no_remote_id(netbox_api_base):
 
 def test_translate_attrs_for_netbox_no_attrs(netbox_api_base):
 
-    config.load(config_data=dict(main=dict(import_vlans=True)))
+    config.load(config_data=dict(main=dict(import_vlans=True, backend="netbox")))
     intf = NetboxInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
     netbox_api_base.add(intf)
 
@@ -72,7 +60,7 @@ def test_translate_attrs_for_netbox_no_attrs(netbox_api_base):
 
 def test_translate_attrs_for_netbox_type(netbox_api_base):
 
-    config.load(config_data=dict(main=dict(import_vlans=True)))
+    config.load(config_data=dict(main=dict(import_vlans=True, backend="netbox")))
     intf = NetboxInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
     netbox_api_base.add(intf)
 
@@ -91,7 +79,7 @@ def test_translate_attrs_for_netbox_type(netbox_api_base):
 
 def test_translate_attrs_for_netbox_description(netbox_api_base):
 
-    config.load(config_data=dict(main=dict(import_vlans=True)))
+    config.load(config_data=dict(main=dict(import_vlans=True, backend="netbox")))
     intf = NetboxInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
     netbox_api_base.add(intf)
 
@@ -112,7 +100,7 @@ def test_translate_attrs_for_netbox_description(netbox_api_base):
 
 def test_translate_attrs_for_netbox_mode(netbox_api_base):
 
-    config.load(config_data=dict(main=dict(import_vlans=True)))
+    config.load(config_data=dict(main=dict(import_vlans=True, backend="netbox")))
     intf = NetboxInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
     netbox_api_base.add(intf)
 
@@ -138,7 +126,7 @@ def test_translate_attrs_for_netbox_vlan(netbox_api_base):
     vlan = NetboxVlan(vid=100, site_name="HQ", remote_id=30)
     netbox_api_base.add(vlan)
 
-    config.load(config_data=dict(main=dict(import_vlans=True)))
+    config.load(config_data=dict(main=dict(import_vlans=True, backend="netbox")))
     intf = NetboxInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
     netbox_api_base.add(intf)
 
@@ -181,7 +169,7 @@ def test_translate_attrs_for_netbox_vlan(netbox_api_base):
 
 def test_translate_attrs_for_netbox_vlan_false(netbox_api_base):
 
-    config.load(config_data=dict(main=dict(import_vlans=False)))
+    config.load(config_data=dict(main=dict(import_vlans=False, backend="netbox")))
     intf = NetboxInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
     netbox_api_base.add(intf)
 
@@ -200,7 +188,7 @@ def test_translate_attrs_for_netbox_vlan_false(netbox_api_base):
 
 def test_translate_attrs_for_netbox_vlan_no(netbox_api_base):
 
-    config.load(config_data=dict(main=dict(import_vlans="no")))
+    config.load(config_data=dict(main=dict(import_vlans="no", backend="netbox")))
     intf = NetboxInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
     netbox_api_base.add(intf)
 
@@ -222,7 +210,7 @@ def test_translate_attrs_for_netbox_lag_member(netbox_api_base):
     parent = NetboxInterface(device_name="HQ-CORE-SW01", name="ge-0/0/4", remote_id=50)
     netbox_api_base.add(parent)
 
-    config.load(config_data=dict(main=dict(import_vlans=False)))
+    config.load(config_data=dict(main=dict(import_vlans=False, backend="netbox")))
     intf = NetboxInterface(device_name="HQ-CORE-SW02", name="ge-0/0/0")
     netbox_api_base.add(intf)
 
diff --git a/tests/unit/adapters/netbox_api/models/test_netbox_ipaddress.py b/tests/unit/adapters/netbox_api/models/test_netbox_ipaddress.py
index f549d592..89d0751d 100644
--- a/tests/unit/adapters/netbox_api/models/test_netbox_ipaddress.py
+++ b/tests/unit/adapters/netbox_api/models/test_netbox_ipaddress.py
@@ -1,16 +1,4 @@
-"""
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""test for NetboxIPAddress model."""
 import os
 import yaml
 import pynetbox
diff --git a/tests/unit/adapters/netbox_api/models/test_netbox_ipaddress_pre29.py b/tests/unit/adapters/netbox_api/models/test_netbox_ipaddress_pre29.py
index adfaf938..804e02cc 100644
--- a/tests/unit/adapters/netbox_api/models/test_netbox_ipaddress_pre29.py
+++ b/tests/unit/adapters/netbox_api/models/test_netbox_ipaddress_pre29.py
@@ -1,16 +1,4 @@
-"""
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""test for NetboxIPAddressPre29 model."""
 import os
 import yaml
 import pynetbox
diff --git a/tests/unit/adapters/netbox_api/models/test_netbox_prefix.py b/tests/unit/adapters/netbox_api/models/test_netbox_prefix.py
index f1c6bc52..9a6431ac 100644
--- a/tests/unit/adapters/netbox_api/models/test_netbox_prefix.py
+++ b/tests/unit/adapters/netbox_api/models/test_netbox_prefix.py
@@ -1,16 +1,4 @@
-"""
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""test for NetboxPrefix model."""
 import os
 import yaml
 
diff --git a/tests/unit/adapters/netbox_api/models/test_netbox_vlan.py b/tests/unit/adapters/netbox_api/models/test_netbox_vlan.py
index 2f264e46..d5dfa72e 100644
--- a/tests/unit/adapters/netbox_api/models/test_netbox_vlan.py
+++ b/tests/unit/adapters/netbox_api/models/test_netbox_vlan.py
@@ -1,16 +1,4 @@
-"""
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""test for NetboxVlan model."""
 import os
 import yaml
 import pytest
diff --git a/tests/unit/adapters/netbox_api/models/test_netbox_vlan_pre29.py b/tests/unit/adapters/netbox_api/models/test_netbox_vlan_pre29.py
index adc30fab..1fb6af96 100644
--- a/tests/unit/adapters/netbox_api/models/test_netbox_vlan_pre29.py
+++ b/tests/unit/adapters/netbox_api/models/test_netbox_vlan_pre29.py
@@ -1,16 +1,4 @@
-"""
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""test for NetboxVlanPre29 model."""
 import os
 import yaml
 import pytest
diff --git a/tests/unit/test_inventory.py b/tests/unit/adapters/netbox_api/test_netbox_inventory.py
similarity index 75%
rename from tests/unit/test_inventory.py
rename to tests/unit/adapters/netbox_api/test_netbox_inventory.py
index 7c15e9ac..7a61d5c2 100644
--- a/tests/unit/test_inventory.py
+++ b/tests/unit/adapters/netbox_api/test_netbox_inventory.py
@@ -1,21 +1,9 @@
-"""
-(c) 2019 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""test for NetboxAPI Inventory."""
 # pylint: disable=E1101
 
 from os import path
 import yaml
-from network_importer.inventory import NetboxInventory
+from network_importer.adapters.netbox_api.inventory import NetBoxAPIInventory
 
 HERE = path.abspath(path.dirname(__file__))
 FIXTURES = "fixtures/inventory"
@@ -38,7 +26,7 @@ def test_nb_inventory_all(requests_mock):
     data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json"))
     requests_mock.get("http://mock/api/dcim/platforms/", json=data2)
 
-    inv = NetboxInventory(nb_url="http://mock", nb_token="12349askdnfanasdf")  # nosec
+    inv = NetBoxAPIInventory(settings=dict(address="http://mock", token="12349askdnfanasdf")).load()  # nosec
 
     assert len(inv.hosts.keys()) == 6
     assert "austin" in inv.hosts.keys()
@@ -68,11 +56,9 @@ def test_nb_inventory_filtered(requests_mock):
     data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json"))
     requests_mock.get("http://mock/api/dcim/platforms/", json=data2)
 
-    inv_filtered = NetboxInventory(  # nosec
-        nb_url="http://mock",  # nosec
-        nb_token="12349askdnfanasdf",  # nosec
-        filter_parameters={"name": "el-paso"},  # nosec
-    )  # nosec
+    inv_filtered = NetBoxAPIInventory(
+        limit="el-paso", settings=dict(address="http://mock", token="12349askdnfanasdf",)  # nosec
+    ).load()  # nosec
 
     assert len(inv_filtered.hosts.keys()) == 1
     assert "el-paso" in inv_filtered.hosts.keys()
@@ -96,11 +82,9 @@ def test_nb_inventory_exclude(requests_mock):
     data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json"))
     requests_mock.get("http://mock/api/dcim/platforms/", json=data2)
 
-    inv = NetboxInventory(  # nosec
-        nb_url="http://mock",  # nosec
-        nb_token="12349askdnfanasdf",  # nosec
-        filter_parameters={"exclude": "platform"},  # nosec
-    )  # nosec
+    inv = NetBoxAPIInventory(
+        settings=dict(address="http://mock", token="12349askdnfanasdf", filter="exclude=platform",)  # nosec  # nosec
+    ).load()  # nosec
 
     assert len(inv.hosts.keys()) == 6
 
@@ -122,7 +106,7 @@ def test_nb_inventory_virtual_chassis(requests_mock):
     data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json"))
     requests_mock.get("http://mock/api/dcim/platforms/", json=data2)
 
-    inv = NetboxInventory(nb_url="http://mock", nb_token="12349askdnfanasdf")  # nosec
+    inv = NetBoxAPIInventory(settings=dict(address="http://mock", token="12349askdnfanasdf")).load()  # nosec
 
     assert len(inv.hosts.keys()) == 2
     assert "test_dev1_2" not in inv.hosts.keys()
@@ -148,19 +132,19 @@ def test_nb_inventory_supported_platforms(requests_mock):
     data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json"))
     requests_mock.get("http://mock/api/dcim/platforms/", json=data2)
 
-    inv = NetboxInventory(  # nosec
-        nb_url="http://mock",  # nosec
-        nb_token="12349askdnfanasdf",  # nosec
+    inv = NetBoxAPIInventory(
         supported_platforms=["ios", "nxos"],  # nosec
-    )  # nosec
+        settings=dict(address="http://mock", token="12349askdnfanasdf")  # nosec  # nosec
+        # nosec
+    ).load()  # nosec
 
     assert len(inv.hosts.keys()) == 2
     assert "austin" in inv.hosts.keys()
     assert "dallas" in inv.hosts.keys()
 
-    inv = NetboxInventory(  # nosec
-        nb_url="http://mock", nb_token="12349askdnfanasdf", supported_platforms=["ios"],  # nosec  # nosec  # nosec
-    )  # nosec
+    inv = NetBoxAPIInventory(  # nosec
+        supported_platforms=["ios"], settings=dict(address="http://mock", token="12349askdnfanasdf")  # nosec
+    ).load()  # nosec
 
     assert len(inv.hosts.keys()) == 1
     assert "austin" in inv.hosts.keys()
diff --git a/tests/unit/adapters/network_importer/test_load_batfish_interface.py b/tests/unit/adapters/network_importer/test_load_batfish_interface.py
index d4607b83..9d79cad9 100644
--- a/tests/unit/adapters/network_importer/test_load_batfish_interface.py
+++ b/tests/unit/adapters/network_importer/test_load_batfish_interface.py
@@ -112,7 +112,7 @@ def test_load_batfish_interface_loopback(network_importer_base, site_sfo, dev_sp
     adapter.add(site_sfo)
     adapter.add(dev_spine1)
 
-    config.load()
+    config.load(config_data=dict(main=dict(backend="nautobot")))
 
     intf = adapter.load_batfish_interface(site=site_sfo, device=dev_spine1, intf=LOOPBACK_1)
 
@@ -128,7 +128,7 @@ def test_load_batfish_interface_phy_intf_ether_std(network_importer_base, site_s
     adapter.add(site_sfo)
     adapter.add(dev_spine1)
 
-    config.load()
+    config.load(config_data=dict(main=dict(backend="nautobot")))
 
     intf = adapter.load_batfish_interface(site=site_sfo, device=dev_spine1, intf=ETHERNET_1)
 
@@ -144,7 +144,7 @@ def test_load_batfish_interface_intf_ether_sub(network_importer_base, site_sfo,
     adapter.add(site_sfo)
     adapter.add(dev_spine1)
 
-    config.load()
+    config.load(config_data=dict(main=dict(backend="nautobot")))
 
     intf = adapter.load_batfish_interface(site=site_sfo, device=dev_spine1, intf=L3_SUB_INTF_1)
 
@@ -163,7 +163,7 @@ def test_load_batfish_interface_intf_lag_member(network_importer_base, site_sfo,
     adapter.add(site_sfo)
     adapter.add(dev_spine1)
 
-    config.load()
+    config.load(config_data=dict(main=dict(backend="nautobot")))
 
     intf = adapter.load_batfish_interface(site=site_sfo, device=dev_spine1, intf=LAG_MEMBER_1)
 
@@ -180,7 +180,7 @@ def test_load_batfish_interface_intf_lag(network_importer_base, site_sfo, dev_sp
     adapter.add(site_sfo)
     adapter.add(dev_spine1)
 
-    config.load()
+    config.load(config_data=dict(main=dict(backend="nautobot")))
 
     intf = adapter.load_batfish_interface(site=site_sfo, device=dev_spine1, intf=LAG_1)
 
@@ -197,7 +197,7 @@ def test_add_batfish_interface_intf_serial(network_importer_base, site_sfo, dev_
     adapter.add(site_sfo)
     adapter.add(dev_spine1)
 
-    config.load()
+    config.load(config_data=dict(main=dict(backend="nautobot")))
 
     intf = adapter.load_batfish_interface(site=site_sfo, device=dev_spine1, intf=SERIAL_1)
 
@@ -216,7 +216,7 @@ def test_load_batfish_interface_description(network_importer_base, site_sfo, dev
     adapter.add(site_sfo)
     adapter.add(dev_spine1)
 
-    config.load()
+    config.load(config_data=dict(main=dict(backend="nautobot")))
 
     data = LOOPBACK_1
     data["description"] = "  intf1 description  "
@@ -234,7 +234,7 @@ def test_load_batfish_intf_no_import_vlans_sub_intf(network_importer_base, site_
     adapter.add(site_sfo)
     adapter.add(dev_spine1)
 
-    config.load(config_data=dict(main=dict(import_vlans=False)))
+    config.load(config_data=dict(main=dict(import_vlans=False, backend="nautobot")))
     intf = adapter.load_batfish_interface(site=site_sfo, device=dev_spine1, intf=L3_SUB_INTF_1)
 
     assert isinstance(intf, Interface)
@@ -248,7 +248,7 @@ def test_load_batfish_intf_no_import_vlans_lag(network_importer_base, site_sfo,
     adapter.add(site_sfo)
     adapter.add(dev_spine1)
 
-    config.load(config_data=dict(main=dict(import_vlans=False)))
+    config.load(config_data=dict(main=dict(import_vlans=False, backend="nautobot")))
     intf = adapter.load_batfish_interface(site=site_sfo, device=dev_spine1, intf=LAG_1)
 
     assert isinstance(intf, Interface)
@@ -262,7 +262,7 @@ def test_load_batfish_intf_no_import_vlans_lag_members(network_importer_base, si
     adapter.add(site_sfo)
     adapter.add(dev_spine1)
 
-    config.load(config_data=dict(main=dict(import_vlans=False)))
+    config.load(config_data=dict(main=dict(import_vlans=False, backend="nautobot")))
     intf = adapter.load_batfish_interface(site=site_sfo, device=dev_spine1, intf=LAG_MEMBER_1)
 
     assert isinstance(intf, Interface)
diff --git a/tests/unit/adapters/network_importer/test_load_batfish_ipaddress.py b/tests/unit/adapters/network_importer/test_load_batfish_ipaddress.py
index c9cdc296..3821236b 100644
--- a/tests/unit/adapters/network_importer/test_load_batfish_ipaddress.py
+++ b/tests/unit/adapters/network_importer/test_load_batfish_ipaddress.py
@@ -25,7 +25,7 @@ def test_load_batfish_ipaddress_w_ip_w_prefix_no_vlan(network_importer_base, sit
     adapter.add(dev_spine1)
     intf1 = Interface(name="et-0/0/0", device_name=dev_spine1.name)
 
-    config.load(config_data=dict(main=dict(import_ips=True, import_prefixes=True)))
+    config.load(config_data=dict(main=dict(backend="nautobot", import_ips=True, import_prefixes=True)))
 
     ipaddr = adapter.load_batfish_ip_address(site=site_sfo, device=dev_spine1, interface=intf1, address="10.10.10.1/24")
     prefix = Prefix(prefix="10.10.10.0/24", site_name="sfo")
@@ -42,7 +42,7 @@ def test_load_batfish_ipaddress_wo_ip_wo_prefix(network_importer_base, site_sfo,
     adapter.add(dev_spine1)
     intf1 = Interface(name="et-0/0/0", device_name=dev_spine1.name)
 
-    config.load(config_data=dict(main=dict(import_ips=False, import_prefixes=False)))
+    config.load(config_data=dict(main=dict(backend="nautobot", import_ips=False, import_prefixes=False)))
 
     ipaddr = adapter.load_batfish_ip_address(site=site_sfo, device=dev_spine1, interface=intf1, address="10.10.10.1/24")
     prefix = Prefix(prefix="10.10.10.0/24", site_name="sfo")
@@ -61,7 +61,7 @@ def test_load_batfish_ipaddress_w_ip_wo_prefix(network_importer_base, site_sfo,
     adapter.add(dev_spine1)
     intf1 = Interface(name="et-0/0/0", device_name=dev_spine1.name)
 
-    config.load(config_data=dict(main=dict(import_ips=True, import_prefixes=False)))
+    config.load(config_data=dict(main=dict(backend="nautobot", import_ips=True, import_prefixes=False)))
 
     ipaddr = adapter.load_batfish_ip_address(site=site_sfo, device=dev_spine1, interface=intf1, address="10.10.10.1/24")
     prefix = Prefix(prefix="10.10.10.0/24", site_name="sfo")
@@ -79,7 +79,7 @@ def test_load_batfish_ipaddress_wo_ip_w_prefix(network_importer_base, site_sfo,
     adapter.add(dev_spine1)
     intf1 = Interface(name="et-0/0/0", device_name=dev_spine1.name)
 
-    config.load(config_data=dict(main=dict(import_ips=False, import_prefixes=True)))
+    config.load(config_data=dict(main=dict(backend="nautobot", import_ips=False, import_prefixes=True)))
 
     ipaddr = adapter.load_batfish_ip_address(site=site_sfo, device=dev_spine1, interface=intf1, address="10.10.10.1/24")
     prefix = Prefix(prefix="10.10.10.0/24", site_name="sfo")
diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py
index 2b8fc33d..8c1fb5ae 100644
--- a/tests/unit/conftest.py
+++ b/tests/unit/conftest.py
@@ -2,6 +2,7 @@
 import pytest
 
 import pynetbox
+import pynautobot
 from diffsync import DiffSync
 from diffsync.diff import DiffElement
 
@@ -14,6 +15,9 @@
 from network_importer.adapters.netbox_api.adapter import NetBoxAPIAdapter
 from network_importer.adapters.netbox_api.models import NetboxSite, NetboxDevice, NetboxInterface, NetboxVlan
 
+from network_importer.adapters.nautobot_api.adapter import NautobotAPIAdapter
+from network_importer.adapters.nautobot_api.models import NautobotSite, NautobotDevice, NautobotInterface, NautobotVlan
+
 
 @pytest.fixture
 def make_site():
@@ -204,7 +208,7 @@ def diff_children_nyc_dev1():
 @pytest.fixture
 def netbox_api_empty():
     """Provide an instance of NetBoxAPIAdapter with pynetbox initiliazed."""
-    diffsync = NetBoxAPIAdapter(nornir=None, config={})
+    diffsync = NetBoxAPIAdapter(nornir=None, settings={})
     diffsync.netbox = pynetbox.api(url="http://mock", token="1234567890")
 
     return diffsync
@@ -213,7 +217,7 @@ def netbox_api_empty():
 @pytest.fixture
 def netbox_api_base():
     """Provide an instance of NetBoxAPIAdapter with pynetbox initiliazed."""
-    diffsync = NetBoxAPIAdapter(nornir=None, config={})
+    diffsync = NetBoxAPIAdapter(nornir=None, settings={})
     diffsync.netbox = pynetbox.api(url="http://mock", token="1234567890")
 
     diffsync.add(NetboxSite(name="HQ", remote_id=10))
@@ -227,7 +231,7 @@ def netbox_api_base():
 @pytest.fixture
 def network_importer_base():
     """Provide an instance of NetworkImporterAdapter with pynetbox initiliazed."""
-    diffsync = NetworkImporterAdapter(nornir=None, config={})
+    diffsync = NetworkImporterAdapter(nornir=None, settings={})
 
     diffsync.add(Site(name="HQ"))
     diffsync.add(Device(name="HQ-CORE-SW02", site_name="HQ", remote_id=29))
@@ -247,3 +251,42 @@ def empty_netbox_query():
         "results": [],
     }
     return value
+
+
+@pytest.fixture
+def nautobot_api_base():
+    """Provide an instance of NautobotAPIAdapter with pynautoboot initiliazed."""
+    diffsync = NautobotAPIAdapter(nornir=None, settings={})
+    diffsync.nautobot = pynautobot.api(url="http://mock_nautobot", token="1234567890")
+
+    diffsync.add(NautobotSite(name="HQ", remote_id="a325e477-62fe-47f0-8b67-acf411b1868f"))
+    diffsync.add(NautobotDevice(name="HQ-CORE-SW02", site_name="HQ", remote_id="e0633a07-c3e2-41b0-a1df-4627392acf0a"))
+    diffsync.add(
+        NautobotInterface(
+            name="TenGigabitEthernet1/0/1", device_name="HQ-CORE-SW02", remote_id="fecc1d8f-99b1-491d-9bdf-1dcb394e27a1"
+        )
+    )
+    diffsync.add(NautobotVlan(vid=111, site_name="HQ", remote_id="464a2de3-fd5e-4b65-a58d-e0a2a617c12e"))
+
+    return diffsync
+
+
+@pytest.fixture
+def nautobot_api_empty():
+    """Provide an instance of NautobotAPIAdapter with pynautobot initiliazed."""
+    diffsync = NautobotAPIAdapter(nornir=None, settings={})
+    diffsync.nautobot = pynautobot.api(url="http://mock", token="1234567890")
+
+    return diffsync
+
+
+@pytest.fixture
+def empty_nautobot_query():
+    """Return an empty list to a list query."""
+    value = {
+        "count": 0,
+        "next": None,
+        "previous": None,
+        "results": [],
+    }
+    return value
diff --git a/tests/unit/processors/test_get_neighbors.py b/tests/unit/processors/test_get_neighbors.py
index f14a0db2..d6570198 100644
--- a/tests/unit/processors/test_get_neighbors.py
+++ b/tests/unit/processors/test_get_neighbors.py
@@ -40,11 +40,11 @@ def nornir(requests_mock):
     requests_mock.get("http://mock/api/dcim/platforms/", json=data2)
 
     nornir = InitNornir(
-        core={"num_workers": 1},
+        runner={"plugin": "threaded", "options": {"num_workers": 1}},
         logging={"enabled": False},
         inventory={
-            "plugin": "network_importer.inventory.NetboxInventory",
-            "options": {"nb_url": "http://mock", "nb_token": "12349askdnfanasdf"},
+            "plugin": "NetBoxAPIInventory",
+            "options": {"settings": {"address": "http://mock", "token": "12349askdnfanasdf"}},
         },
     )
 
@@ -65,7 +65,7 @@ def dispatch_get_neighbors(task, **kwargs):
 
 def test_base(nornir):
     """Validate that the processor is working as expected with standard inputs."""
-    config.load()
+    config.load(config_data=dict(main=dict(backend="nautobot")))
 
     neighbors = Neighbors()
     neighbors.neighbors["intfa"].append(Neighbor(hostname="devicea", port="intfa"))
@@ -84,7 +84,7 @@ def test_base(nornir):
 
 def test_cleanup_fqdn(nornir):
     """Validate that we are cleaning up the FQDN from device name."""
-    config.load(config_data={"network": {"fqdns": ["test.com"]}})
+    config.load(config_data=dict(main=dict(backend="nautobot"), network=dict(fqdns=["test.com"])))
 
     neighbors = Neighbors()
     neighbors.neighbors["intfa"].append(Neighbor(hostname="devicea.test.com", port="intfa"))
@@ -103,7 +103,7 @@ def test_cleanup_fqdn(nornir):
 
 def test_cleanup_mac_address(nornir):
     """Validate that we are removing neighbor with a mac address name."""
-    config.load(config_data={"network": {"fqdns": ["test.com"]}})
+    config.load(config_data=dict(main=dict(backend="nautobot"), network=dict(fqdns=["test.com"])))
 
     neighbors = Neighbors()
     neighbors.neighbors["intfa"].append(Neighbor(hostname="devicea.test.com", port="intfa"))
@@ -122,7 +122,7 @@ def test_cleanup_mac_address(nornir):
 
 def test_cleanup_port(nornir):
     """Validate that we are cleaning up the port from port name."""
-    config.load(config_data={"network": {"fqdns": ["test.com"]}})
+    config.load(config_data=dict(main=dict(backend="nautobot"), network=dict(fqdns=["test.com"])))
 
     neighbors = Neighbors()
     neighbors.neighbors["intfa"].append(Neighbor(hostname="devicea", port="HundredGigE0/0/0/0"))
diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py
new file mode 100644
index 00000000..8b6bd764
--- /dev/null
+++ b/tests/unit/test_config.py
@@ -0,0 +1,39 @@
+"""unit test for config related functions."""
+import pytest
+
+from network_importer.exceptions import ConfigLoadFatalError
+from network_importer.config import _configure_backend, Settings, DEFAULT_BACKENDS
+
+TEST_BACKEND = list(DEFAULT_BACKENDS.keys())[0]
+
+
+def test_configure_backend_w_backend():
+
+    result = _configure_backend(Settings(main=dict(backend=TEST_BACKEND)))
+    assert result.inventory.inventory_class is not None
+    assert result.adapters.sot_class is not None
+
+    result = _configure_backend(
+        Settings(main=dict(backend=TEST_BACKEND), inventory=dict(inventory_class="myinventoryclass"))
+    )
+    assert result.inventory.inventory_class == "myinventoryclass"
+    assert result.adapters.sot_class is not None
+
+    result = _configure_backend(Settings(main=dict(backend=TEST_BACKEND), adapters=dict(sot_class="mysotclass")))
+    assert result.inventory.inventory_class is not None
+    assert result.adapters.sot_class == "mysotclass"
+
+
+def test_configure_backend_wo_backend():
+
+    with pytest.raises(ConfigLoadFatalError):
+        _configure_backend(Settings())
+
+    with pytest.raises(ConfigLoadFatalError):
+        _configure_backend(Settings(inventory=dict(inventory_class="myinventoryclass")))
+
+    result = _configure_backend(
+        Settings(adapters=dict(sot_class="mysotclass"), inventory=dict(inventory_class="myinventoryclass"))
+    )
+    assert result.adapters.sot_class == "mysotclass"
+    assert result.inventory.inventory_class == "myinventoryclass"
diff --git a/tests/unit/test_diff.py b/tests/unit/test_diff.py
index 145077ca..e061e607 100644
--- a/tests/unit/test_diff.py
+++ b/tests/unit/test_diff.py
@@ -1,16 +1,4 @@
-"""
-(c) 2020 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""Unit tests for NetworkImporterDiff."""
 from collections.abc import Iterable
 
 from network_importer.diff import NetworkImporterDiff
diff --git a/tests/unit/test_performance.py b/tests/unit/test_performance.py
index 8c80c554..05aad2b7 100644
--- a/tests/unit/test_performance.py
+++ b/tests/unit/test_performance.py
@@ -1,16 +1,4 @@
-"""
-(c) 2019 Network To Code
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-  http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+"""unit tests for network_importer.performance."""
 
 from network_importer.performance import print_from_ms