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

Issue:
Adding Custom UWSGI Testing



Discussion / hints:

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 00000000..72e343dd
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,178 @@
+---
+version: 2.1
+aliases:
+  - &linting_and_unit_tests
+    - "linting"
+    - "test"
+  - &linting_test
+    - "linting"
+  - &unit_test
+    - "test"
+executors:
+  linux:
+    docker:
+      - image: "circleci/python:3.7.10"
+orbs:
+  # The python orb contains a set of prepackaged CircleCI configuration you can use repeatedly in your configuration
+  # files
+  # Orb commands and jobs help you with common scripting around a language/tool
+  # so you dont have to copy and paste it everywhere.
+  # See the orb documentation here: https://circleci.com/developer/orbs/orb/circleci/python
+  python: "circleci/python@1.2"
+workflows:
+  TestFlow:  # This is the name of the workflow, feel free to change it to better match your workflow.
+    # Inside the workflow, you define the jobs you want to run.
+    # For more details on extending your workflow, see the configuration docs:
+    # https://circleci.com/docs/2.0/configuration-reference/#workflows
+    jobs:
+      - "linting"
+      - test:
+          requires: *linting_test
+      - "integration_nautobot":
+          requires: *unit_test
+      - "integration_netbox28":
+          requires: *unit_test
+      - "integration_netbox29":
+          requires: *unit_test
+      - "integration_netbox210":
+          requires: *unit_test
+
+jobs:
+  linting:
+    docker:
+      - image: "circleci/python:3.7.10"  # primary container for the build job
+        environment:
+          INVOKE_LOCAL: "True"
+    steps:
+      - "checkout"
+      # - python/install-packages:
+      #     pkg-manager: poetry
+      - run:
+          command: "pip install --upgrade wheel pip"
+          name: "Upgrade Pip"
+      - run:
+          command: "pip install invoke poetry toml"
+          name: "Install pip packages"
+      - run:
+          command: "poetry config virtualenvs.create false"
+          name: "Poetry config virtual envs"
+      - run:
+          command: "poetry config installer.parallel false"
+          name: "Poetry config parallel"
+      - run:
+          command: "poetry install --no-interaction --no-ansi --no-root"
+          name: "Install application"
+      - run:
+          command: "invoke black"
+      - run:
+          command: "invoke bandit"
+      - run:
+          command: "invoke pydocstyle"
+      - run:
+          command: "invoke flake8"
+      - run:
+          command: "invoke yamllint"
+      - run:
+          command: "invoke pylint"
+  test:
+    machine:
+      image: "ubuntu-2004:202104-01"
+    steps:
+      - "checkout"
+      - run:
+          name: "Change to Python 3.7 package on system"
+          command: |
+            pyenv versions
+            pyenv global 3.9.4
+            which pip
+            pip -V
+      - run:
+          name: "Install python packages"
+          command: "pip install --upgrade wheel pip toml invoke"
+      - run:
+          name: "Build Test Image"
+          command: "invoke build-image --nocache"
+      - run:
+          name: "Run Unit Tests"
+          command: "invoke pytest"
+  integration_nautobot:
+    machine:
+      image: "ubuntu-2004:202104-01"
+    steps:
+      - "checkout"
+      - run:
+          name: "Nautobot Test"
+          command: |
+            pyenv versions
+            pyenv global 3.9.4
+            which pip
+            pip -V
+            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
+            invoke nautobot-integration-tests
+          environment:
+            NAUTOBOT_VERSION: "1.0.1"
+  integration_netbox210:
+    machine:
+      image: "ubuntu-2004:202104-01"
+    steps:
+      - "checkout"
+      - run:
+          name: "NetBox 2.10 Test"
+          command: |
+            pyenv versions
+            pyenv global 3.9.4
+            which pip
+            pip -V
+            pip install invoke toml poetry pynetbox ansible==2.10.7
+            ansible-galaxy collection install git+https://github.com/netbox-community/ansible_modules.git,v3.0.0
+            poetry config virtualenvs.create false
+            poetry config installer.parallel false
+            poetry install
+            invoke integration-tests
+          environment:
+            NETBOX_VERSION: "v2.10"
+  integration_netbox29:
+    machine:
+      image: "ubuntu-2004:202104-01"
+    steps:
+      - "checkout"
+      - run:
+          name: "NetBox 2.9 Test"
+          command: |
+            pyenv versions
+            pyenv global 3.9.4
+            which pip
+            pip -V
+            pip install invoke toml poetry pynetbox ansible==2.10.7
+            ansible-galaxy collection install git+https://github.com/netbox-community/ansible_modules.git,v3.0.0
+            poetry config virtualenvs.create false
+            poetry config installer.parallel false
+            poetry install
+            invoke integration-tests
+          environment:
+            NETBOX_VERSION: "v2.9"
+  integration_netbox28:
+    machine:
+      image: "ubuntu-2004:202104-01"
+    steps:
+      - "checkout"
+      - run:
+          name: "NetBox 2.8 Test"
+          command: |
+            pyenv versions
+            pyenv global 3.9.4
+            which pip
+            pip -V
+            pip install invoke toml poetry pynetbox ansible==2.10.7
+            ansible-galaxy collection install git+https://github.com/netbox-community/ansible_modules.git,v3.0.0
+            poetry config virtualenvs.create false
+            poetry config installer.parallel false
+            poetry install
+            invoke integration-tests
+          environment:
+            NETBOX_VERSION: "v2.8"
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
deleted file mode 100644
index 72a44138..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,123 +0,0 @@
----
-# Add additional stages in the order of execution here, and then under the job:include: key
-dist: "focal"  # Ubuntu 20.04
-stages:
-  - name: "lint"
-  - name: "test"
-  - name: "integration"
-  - name: "deploy-github"
-    if: "tag IS present"
-  - name: "deploy-pypi"
-    if: "tag IS present"
-
-language: "python"
-python:
-  - 3.6
-  - 3.7
-  - 3.8
-  - 3.9
-
-services:
-  - "docker"
-
-# Poetry 1.1.0 added parallel installation as an option;
-# unfortunately it seems to have some issues with installing/updating "requests" and "certifi"
-# while simultaneously attempting to *use* those packages to install other packages.
-# For now we disable it with "poetry config installer.parallel false"
-
-# before_install, Script & before_script for test stage
-before_install: >
-  echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin || travis_terminate 1
-
-before_script:
-  - "pip install invoke"
-  - "invoke build-image --nocache"
-script:
-  - "invoke pytest"
-
-jobs:
-  include:
-    - stage: "lint"
-      env:
-        - "INVOKE_LOCAL=True"
-      before_script:
-        - "pip install invoke poetry toml"
-        - "poetry config virtualenvs.create false"
-        - "poetry config installer.parallel false"
-        - "poetry install --no-interaction --no-ansi --no-root"
-      script:
-        - "invoke black"
-        - "invoke bandit"
-        - "invoke pydocstyle"
-        - "invoke flake8"
-        - "invoke yamllint"
-        - "invoke pylint"
-      python: 3.7
-
-    - stage: "integration"
-      name: "Netbox v2.10"
-      before_install: >
-        echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin || travis_terminate 1
-      before_script:
-        - "pip install invoke toml poetry pynetbox ansible==2.10.7"
-        - "ansible-galaxy collection install git+https://github.com/netbox-community/ansible_modules.git,v3.0.0"
-        - "poetry config virtualenvs.create false"
-        - "poetry config installer.parallel false"
-        - "poetry install"
-      script:
-        - "invoke integration-tests"
-      env: "NETBOX_VERSION=v2.10"
-      python: 3.7
-
-    - stage: "integration"
-      name: "Netbox v2.9"
-      before_install: >
-        echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin || travis_terminate 1
-      before_script:
-        - "pip install invoke toml poetry pynetbox ansible==2.10.7"
-        - "ansible-galaxy collection install git+https://github.com/netbox-community/ansible_modules.git,v3.0.0"
-        - "poetry config virtualenvs.create false"
-        - "poetry config installer.parallel false"
-        - "poetry install"
-      script:
-        - "invoke integration-tests"
-      env: "NETBOX_VERSION=v2.9"
-      python: 3.7
-
-    - stage: "integration"
-      name: "Netbox v2.8"
-      before_install: >
-        echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin || travis_terminate 1
-      before_script:
-        - "pip install invoke toml poetry pynetbox ansible==2.10.7"
-        - "ansible-galaxy collection install git+https://github.com/netbox-community/ansible_modules.git,v3.0.0"
-        - "poetry config virtualenvs.create false"
-        - "poetry config installer.parallel false"
-        - "poetry install"
-      script:
-        - "invoke integration-tests"
-      env: "NETBOX_VERSION=v2.8"
-      python: 3.7
-
-    - stage: "deploy-github"
-      before_script:
-        - "pip install poetry"
-      script:
-        - "poetry version $TRAVIS_TAG"
-        - "poetry build"
-      deploy:
-        provider: "releases"
-        api_key: "$GITHUB_AUTH_TOKEN"
-        file_glob: true
-        file: "dist/*"
-        skip_cleanup: true
-        "on":
-          all_branches: true
-
-    - stage: "deploy-pypi"
-      before_script:
-        - "pip install poetry"
-      script:
-        - "poetry version $TRAVIS_TAG"
-        - "poetry config pypi-token.pypi $PYPI_TOKEN"
-        - "poetry publish --build"
diff --git a/LICENSE b/LICENSE
index 2a07d287..27a9f15b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright 2020 Network to Code <info@networktocode.com>
+Copyright 2021 Network to Code <opensource@networktocode.com>
 Network to Code, LLC
 
 Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/README.md b/README.md
index a53301e5..fd511a62 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ The main use cases for the network importer:
 - [Configuration file](docs/configuration.md)
 - [Supported Features and Architecture](docs/architecture.md)
 - [Extensibility](docs/extensibility.md)
+- [Upgrade procedure](docs/upgrade.md)
 
 ## Questions
 
diff --git a/docs/architecture.md b/docs/architecture.md
index faf076e9..92406291 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -1,12 +1,12 @@
 
 # Architecture
 
-Internally the network-importer is leveraging the [diffsync](https://github.com/networktocode/diffsync) library to compare the state of the network and the state of the Source of Truth. The diffsync library is designed to compare the state of 2 **adapters** based on a shared data-models.
+Internally the network-importer is leveraging the [diffsync](https://github.com/networktocode/diffsync) library to compare the state of the network and the state of the Source of Truth. [The diffsync library is designed to compare the state of 2 **adapters** based on a shared data-models](https://blog.networktocode.com/post/intro-to-diffing-and-syncing-data-with-diffsync/).
 
 To communicate to the network devices, the network importer is leveraging [Nornir](https://github.com/nornir-automation/nornir) and incorporate a concept of **drivers** to easily add support for more platforms if needed.
 By default the Network Importer supports 3 main actions to execute against the network devices:
 - `get_config`: Retrieve the running configuration and store it in the `config_directory` folder.
-- `get_neighbors`: Retrieve the list of all neighbors, eiher from LLDP or CDP (based on the configuration)
+- `get_neighbors`: Retrieve the list of all neighbors, either from LLDP or CDP (based on the configuration)
 - `get_vlans`: Retrieve the list of vlans present on the device.
 
 ## Internal Datamodel
@@ -22,13 +22,35 @@ The internal/shared datamodel is defined in the [network_importer/models.py](../
 
 > It's possible to extend the default models and add your own, please check the [extensibility section](extensibility.md) of the documentation
 
-## Adapters
+## Backend, Adapters & Inventory
+
+To operate the Network Importer needs 1 inventory and 2 adapters:
+- An inventory to get the list of devices to analyze and get the minimum information to connect to them (platform, address, cred
+entials ..)
+- One adapter to read the information from the network and one to read/write information to the Source of Truth backend.
+
+Since the inventory is usually leveraging the SOT, the SOT adapter and the inventory are packaged into a **backend**. Both Nautobot and Netbox are supported as backend systems. When a specific backend is selected it will update both the SOT adapter and the inventory.
+With or without leveraging the default backends, it's possible to provide your own adapter or extend one of the default adapters.
+
+> The base adapter for Network Importer is defined in [network_importer/adapters/base.py](../network_importer/adapters/base.py). The main difference with a standard diffsync adapter is that a Network Importer adapter needs to accept a NetworkImporter inventory (based on a Nornir inventory) as parameters at init time (nornir).
 
-To operate the Network Importer needs 2 adapters. 
+### Nautobot API Adapter
 
-An adapter to read the information from the network and one to read/write information to Netbox via its Rest API are provided by default but it's possible to provide your own adapter or extend either or both default adapters.
+The Nautobot API adapter is designed to read the status of a Nautobot server over its Rest API and update Nautobot based on the status of the network.
 
-> The base adapter for Network Importer is defined in [network_importer/adapters/base.py](../network_importer/adapters/base.py). The main difference with a standard diffsync adapter is that a Network Importer adapter needs to accept a nornir inventory as parameters at init time (nornir).
+The table below present the capabilities in term of : Read, Create, Update and Delete supported for each model by the netbox_api adapter.
+
+| Model              | Inherit from | Create | Read   | Update | Delete |
+|--------------------|--------------|--------|--------|--------|--------|
+| NautobotSite       | Site         | No     | Yes    | No     | No     | 
+| NautobotDevice     | Device       | No     | Yes    | No     | No     | 
+| NautobotInterface  | Interface    | Yes    | Yes    | Yes    | Yes    | 
+| NautobotIPAddress  | IPAddress    | Yes    | Yes    | Yes    | Yes    | 
+| NautobotPrefix     | Prefix       | Yes    | Yes    | Yes    | No     | 
+| NautobotVlan       | Vlan         | Yes    | Yes    | Yes    | No     | 
+| NautobotCable      | Cable        | Yes    | Yes    | No     | No     | 
+
+> It's possible to extend the default models and add your own, please check the [extensibility section](extensibility.md) of the documentation
 
 ### NetBox API Adapter
 
@@ -54,7 +76,7 @@ The Network Importer Adapter is designed to read the status of the network prima
 
 ## Drivers
 
-The communicate with the network devices, the network importer is leveraging Nornir and support some drivers per platform to easily support more device type.
+The communicate with the network devices, the network-importer is leveraging Nornir and support some drivers per platform to easily support more device type.
 
 Each driver, should support each of the following actions: 
 - `get_config`: Retrieve the running configuration and store it in the `config_directory` folder.
@@ -72,7 +94,7 @@ By default, 4 drivers are available `default`, `cisco_default`, `juniper_junos`
 | juniper_junos   | juniper_junos  |
 | arista_eos      | arista_eos     |
 
-> The name of the platform must match the name of the slug platform defined in NetBox for a given device
+> The name of the platform must match the name of the slug platform defined in the inventory for a given device
 
 ### Drivers available by default
 
diff --git a/docs/backend/nautobot.md b/docs/backend/nautobot.md
new file mode 100644
index 00000000..55a53781
--- /dev/null
+++ b/docs/backend/nautobot.md
@@ -0,0 +1,39 @@
+
+# Nautobot Backend
+
+The Nautobot backend is composed of an inventory and a SOT adapter designed to work with Nautobot 1.0+.
+Both the inventory and the SOT adapter can accept some specific settings as described below.
+
+## Inventory
+
+### Available Settings
+
+```toml
+[inventory.settings]
+
+address = "http://localhost:8080"                   # Alternative Env Variable : NAUTOBOT_ADDRESS
+token = "113954578a441fbe487e359805cd2cb6e9c7d317"  # Alternative Env Variable : NAUTOBOT_TOKEN
+verify_ssl = true                                   # Alternative Env Variable : NAUTOBOT_VERIFY_SSL
+
+
+# The default method is to use the primary IP defined in Nautobot.
+# 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 = "mydomain.com"
+
+# Optional filter to limit the scope of the inventory, takes a comma separated string of key value pair"
+filter = "site=XXX,site=YYY,status=active"    # Alternative Env Variable : INVENTORY_FILTER
+```
+
+## SOT Adapter
+
+### Available Settings
+```toml
+[adapters.sot_settings]
+# 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
+```
\ No newline at end of file
diff --git a/docs/backend/netbox.md b/docs/backend/netbox.md
new file mode 100644
index 00000000..4ce6a0e6
--- /dev/null
+++ b/docs/backend/netbox.md
@@ -0,0 +1,39 @@
+
+# Netbox Backend
+
+The NetBox backend is composed of an inventory and a SOT adapter designed to work with NetBox 2.8+.
+Both the inventory and the SOT adapter can accept some specific settings as described below.
+
+## Inventory
+
+### Available Settings
+
+```toml
+[inventory.settings]
+
+address = "http://localhost:8080"                   # Alternative Env Variable : NETBOX_ADDRESS
+token = "113954578a441fbe487e359805cd2cb6e9c7d317"  # Alternative Env Variable : NETBOX_TOKEN
+verify_ssl = true                                   # Alternative Env Variable : NETBOX_VERIFY_SSL
+
+
+# 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 = "mydomain.com"
+
+# Optional filter to limit the scope of the inventory, takes a comma separated string of key value pair"
+filter = "site=XXX,site=YYY,status=active"    # Alternative Env Variable : INVENTORY_FILTER
+```
+
+## SOT Adapter
+
+### Available Settings
+```toml
+[adapters.sot_settings]
+# 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
+```
\ No newline at end of file
diff --git a/docs/configuration.md b/docs/configuration.md
index 6c5903ee..29870fdc 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1,10 +1,10 @@
 # Configuration File
 
-By default the network importer will try to load a configuration file name `network_importer.toml`, this configuration file is not mandatory as long as the required information to connect to NetBox, Batfish and/or the network devices are provided via environment variables.
+By default the network importer will try to load a configuration file name `network_importer.toml`, this configuration file is not mandatory as long as the required information to connect to the SOT, Batfish and/or the network devices are provided via environment variables.
 
 It's possible to specify which configuration file should be used in cli with the option `--config`.
 
-The configuration file is organized in 8 sections detailed below. 
+The configuration file is organized in 7 sections detailed below. 
 
 ## Main Section
 
@@ -32,53 +32,48 @@ import_vlans = "config"         # Valid options are ["cli", "config", "no", true
 import_cabling = "lldp"         # 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
+# Number of Nornir tasks to execute at the same time
+nbr_workers = 25
 
 # Directory where the configuration can be find, organized in Batfish format
 configs_directory = "configs"
+
+# Valid Backend
+# 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.
+backend = "nautobot"
 ```
 
-# NetBox Section
+# Inventory Section
 
-The `[netbox]` section regroup all parameters to connect to NetBox and it also include an optional list of supported platforms.
+The `[inventory]` section regroup all parameters related to the inventory and also includes an optional list of supported platforms. network-importer supports multiple inventories, you can define your own inventory by defining `inventory_class` and each inventory can define its own list of settings which will need to be configurated under `[inventory.settings]`. The settings for the [nautobot inventory](backend/nautobot.md) and for the [netbox inventory](backend/netbox.md) are available in their respective documentation.
 
 ```toml
-[netbox]
-address = "http://localhost:8080"                   # Alternative Env Variable : NETBOX_ADDRESS
-token = "113954578a441fbe487e359805cd2cb6e9c7d317"  # Alternative Env Variable : NETBOX_TOKEN
-verify_ssl = true                                   # Alternative Env Variable : NETBOX_VERIFY_SSL
+[inventory]
 
 # 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
-```
-
-
-    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.
+# Configure which Inventory will be loaded by the network importer.
+inventory_class = "network_importer.inventory.NetboxInventory"
 
+[inventory.settings]
+# Inventory specific settings, please refer to the documentation of each backend/inventory.
+```
 
 ## Batfish Section
 
 The `[batfish]` section regroup all parameters to connect to Batfish.
 ```toml
 [batfish]
-address = "localhost"   # Alternative Env Variable : BATFISH_ADDRESS
-api_key = "XXXX"        # Alternative Env Variable : BATFISH_API_KEY
-network_name = "network-importer"
-snapshot_name = "latest"
-port_v1 = 9997
-port_v2 = 9996
-use_ssl = false
-api_key = "XXXX"       # API KEY to connect to Batfish enterprise instance
+address = "localhost"               # Alternative Env Variable : BATFISH_ADDRESS
+api_key = "XXXX"                    # Alternative Env Variable : BATFISH_API_KEY
+network_name = "network-importer"   # Alternative Env Variable : BATFISH_NETWORK_NAME
+snapshot_name = "latest"            # Alternative Env Variable : BATFISH_SNAPSHOT_NAME
+port_v1 = 9997                      # Alternative Env Variable : BATFISH_PORT_V1
+port_v2 = 9996                      # Alternative Env Variable : BATFISH_PORT_V2
+use_ssl = false                     # Alternative Env Variable : BATFISH_USE_SSL
 ```
 
 ## Network Section
@@ -93,43 +88,40 @@ login = "username"          # Alternative Env Variable : NETWORK_DEVICE_LOGIN
 password = "password"       # 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 Section
 
-Define what method should be used to connect to the network devices. 
+[network.netmiko_extras]
+# Any additional parameters for Netmiko defined in this section will be automatically configured 
+# as part of the Nornir inventory.
 
-```toml
-[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 = "mydomain.com"
-
-# Optional filter to limit the scope of the inventory, takes a comma separated string of key value pair"
-filter = "site=XXX,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"
+[network.napalm_extras]
+# Any additional parameters for Napalm defined in this section will be automatically configured 
+# as part of the Nornir inventory.
 ```
 
 ## Adapters Section
 
 Configure which adapters will be loaded by the network importer.
 Please see the [extensibility section](extensibility.md) of the documentation for more details on how to create your own adapter.
+The settings for the [nautobot adapter](backend/nautobot.md) and for the [netbox adapter](backend/netbox.md) are available in their respective documentation.
 
 ```toml
 [adapters]
 network_class = "network_importer.adapters.network_importer.adapter.NetworkImporterAdapter"
+
+
 sot_class = "network_importer.adapters.netbox_api.adapter.NetBoxAPIAdapter"
+
+[adapters.network_settings]
+# Network Adapter specific settings, any settings defined in this section will be passed to the Network Adapter. 
+# The default network adapter doesn't accept additional settings, nonetheless this section remains available if you want to
+# customize the default Network Adapter
+
+[adapters.sot_settings]
+# SOT Adapter specific settings, any settings defined in this section will be passed to the SOT Adapter. 
+# Please refer to the documentation of each adapter to see what settings are required/supported.
 ```
 
 ## Drivers Section
diff --git a/docs/extensibility.md b/docs/extensibility.md
index a996ff33..59a074a0 100644
--- a/docs/extensibility.md
+++ b/docs/extensibility.md
@@ -7,9 +7,10 @@ The extensibility principles leverages Python object extensibility. It's recomme
 You can use [setuptools](https://python-packaging-tutorial.readthedocs.io/en/latest/setup_py.html) or [Poetry](http://blog.networktocode.com/post/upgrade-your-python-project-with-poetry/) to quickly create a new python package.
 
 There are different ways the Network Importer can be extended depending on what you are trying to achieve:
-- [Add support for new device platform by extending the default drivers](#extend-the-default-drivers)
-- [Add support for new features buy extending the adapters](#extend-an-existing-adapter-or-provide-your-own)
-- [Add support for new data by extending the models](#extend-the-default-models)
+  - [Extend the default drivers](#extend-the-default-drivers)
+  - [Extend an existing adapter or provide your own](#extend-an-existing-adapter-or-provide-your-own)
+  - [Extend the default inventory](#extend-the-default-inventory)
+  - [Extend the default models](#extend-the-default-models)
 
 ## Extend the default drivers
 
@@ -40,7 +41,8 @@ class NetworkImporterDriver(DefaultNetworkImporterDriver):
             Result: Nornir Result object with a dict as a result containing the running configuration
                 { "config: <running configuration> }
         """
-        pass 
+        pass
+
     @staticmethod
     def get_neighbors(task: Task) -> Result:
         """Get a list of neighbors from the device.
@@ -99,7 +101,7 @@ class MyAdapter(NetworkImporterAdapter):
 
 ### Create a new adapter
 
-All adapters must implement the `load` method. The load method is called during the initilization process and is expected to load all the data from the remote system into the local cache, following the Models defined.
+All adapters must implement the `load` method. The load method is called during the initialization process and is expected to load all the data from the remote system into the local cache, following the Models defined.
 
 ```python
 from network_importer.adapters.base import BaseAdapter
@@ -120,6 +122,14 @@ network_class = "my_python_package.adapters.MyAdapter"
 sot_class = "my_python_package.adapters.MyOtherAdapter"
 ```
 
+## Extend the default inventory
+
+It's possible to extend the default inventory or provide your own inventory.
+
+A Network Importer inventory must be a valid Nornir 3.x inventory and it must be based of the NetworkImporterInventory class.
+
+Once you have created your own inventory, you need to register it with Nornir in order for Nornir to successfully load it. You than need to define your inventory name in [inventory.inventory_class]
+
 ## Extend the default models
 
 It's possible to extend the default models by creating new classes and attaching them to an adapter.
diff --git a/docs/getting_started.md b/docs/getting_started.md
index 9d3b8f17..b4cd7099 100644
--- a/docs/getting_started.md
+++ b/docs/getting_started.md
@@ -12,20 +12,47 @@ pip install network-importer
 ### Pre-requisite
 
 To operate, the Network Importer is dependents on the following items:
-- A device inventory (defined in NetBox for now)
+- A device inventory (defined in Nautobot or NetBox for now)
 - Batfish 
 - A valid configuration file
 
 #### Inventory
 
-A device inventory must be already available in NetBox, if you don't have your devices in NetBox yet you can use the [onboarding plugin for NetBox](https://github.com/networktocode/ntc-netbox-plugin-onboarding/) to easily import your devices. 
+A device inventory must be already available in Nautobot or NetBox, if you don't have your devices in Nautobot or NetBox yet you can use the [onboarding plugin for Nautobot](https://github.com/nautobot/nautobot-plugin-device-onboarding) or the [onboarding plugin for NetBox](https://github.com/networktocode/ntc-netbox-plugin-onboarding/) to easily import your devices. 
 
-To be able to connect to the device the following information needs to be defined in NetBox:
+To be able to connect to the device the following information needs to be defined :
 - Primary ip address (or valid fqdn)
-- Platform (must be a valid Netmiko driver or have a valid napalm driver defined)
+- Platform (must be a valid Netmiko driver or have a valid Napalm driver defined)
 
 > Connecting to the device is not mandatory but some features depends on it: configuration update, mostly cabling and potentially vlan update.
 
+##### Validate your inventory
+
+You can validate the status of your inventory with the command `network-importer inventory --check-connectivity`
+
+```
+# network-importer inventory
+                                           Device Inventory (all)
+┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+┃ Device                     ┃ Platform      ┃ Driver                                 ┃ Reachable ┃ Reason                          ┃
+┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
+│ jcy-bb-01.infra.ntc.com    │ cisco_ios     │ network_importer.drivers.cisco_default │ True      │                                 │
+│ jcy-rtr-01.infra.ntc.com   │ cisco_ios     │ network_importer.drivers.cisco_default │ True      │                                 │
+│ jcy-rtr-02.infra.ntc.com   │ cisco_ios     │ network_importer.drivers.cisco_default │ True      │                                 │
+│ jcy-spine-01.infra.ntc.com │ cisco_nxos    │ network_importer.drivers.cisco_default │ True      │                                 │
+│ jcy-spine-02.infra.ntc.com │ cisco_nxos    │ network_importer.drivers.cisco_default │ True      │                                 │
+│ nyc-bb-01.infra.ntc.com    │ juniper_junos │ network_importer.drivers.juniper_junos │ True      │                                 │
+│ nyc-leaf-01.infra.ntc.com  │ arista_eos    │ network_importer.drivers.arista_eos    │ False     │ device not reachable on port 22 │
+│ nyc-leaf-02.infra.ntc.com  │ arista_eos    │ network_importer.drivers.arista_eos    │ False     │ device not reachable on port 22 │
+│ nyc-rtr-01.infra.ntc.com   │ juniper_junos │ network_importer.drivers.juniper_junos │ False     │ device not reachable on port 22 │
+│ nyc-rtr-02.infra.ntc.com   │ juniper_junos │ network_importer.drivers.juniper_junos │ False     │ device not reachable on port 22 │
+│ nyc-spine-01.infra.ntc.com │ arista_eos    │ network_importer.drivers.arista_eos    │ False     │ device not reachable on port 22 │
+│ nyc-spine-02.infra.ntc.com │ arista_eos    │ network_importer.drivers.arista_eos    │ False     │ device not reachable on port 22 │
+└────────────────────────────┴───────────────┴────────────────────────────────────────┴───────────┴─────────────────────────────────┘
+```
+
+> the option `--check-connectivity` will try to connect to all devices on port 22 (tcp_ping)
+
 #### Batfish
 
 The Network Importer requires to have access to a working batfish environment, you can easily start one using docker or use a Batfish Enterprise instance.
@@ -54,21 +81,20 @@ Please check the [documentation of the configuration file](configuration.md) for
 # Directory where the configurations can be find, organized in Batfish format
 # configs_directory= "configs"
 
-[inventory]
-# Limit the scope of the inventory to a subset of devices
-# Accept any parameters supported by NetBox devices API
-# filter = "site=nyc,role=router"
-# filter = ""
+backend = "nautobot"            # Valid options are ["nautobot", "netbox"]
 
+[inventory]
 # 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" ]
 
-[netbox]
-# The information to connect to NetBox needs to be provided, either in the config file or as environment variables
-address = "http://localhost:8080"                   # Alternative Env Variable : NETBOX_ADDRESS
-token = "113954578a441fbe487e359805cd2cb6e9c7d317"  # Alternative Env Variable : NETBOX_TOKEN
-verify_ssl = true                                   # Alternative Env Variable : NETBOX_VERIFY_SSL
+[inventory.settings]
+# The information to connect to Nautobot needs to be provided, either in the config file or as environment variables
+# These settings are specific to the Nautobot inventory, please check the documentation of your inventory for the 
+# exist list of of available settings.
+address = "http://localhost:8080"                   # Alternative Env Variable : NAUTOBOT_ADDRESS
+token = "113954578a441fbe487e359805cd2cb6e9c7d317"  # Alternative Env Variable : NAUTOBOT_TOKEN
+verify_ssl = true                                   # Alternative Env Variable : NAUTOBOT_VERIFY_SSL
 
 [network]
 # To be able to pull live information from the devices, the credential information needs to be provided
diff --git a/docs/upgrade.md b/docs/upgrade.md
new file mode 100644
index 00000000..05267423
--- /dev/null
+++ b/docs/upgrade.md
@@ -0,0 +1,40 @@
+
+# Upgrade procedures
+
+For all upgrade, it's mandatory to upgrade all dependencies and to validate what is the recommended version of Batfish. Please refer to the individual sections below for specific instructions.
+
+## Upgrade from 2.0 to 3.0
+
+The format of the configuration has changed between version 2.0 and 3.0.
+Please check the new format of the configuration and see below some pointers to convert your configuration file to the new format.
+
+### Update your configuration file
+#### main 
+- `backend` is now a mandatory setting in the configuration. 
+
+#### inventory section
+`fqdn`, `filter` and `use_primary_ip` have been removed from the inventory section, these settings are now part of the Netbox & Nautobot inventory settings, available under `[inventory.settings]`.
+`supported_platforms` has been moved to the `[inventory]` section, it was previously under `[netbox]`
+
+A new section [inventory.settings] is available and everything in this section will be passed to the inventory class at runtime. This allow each inventory to define and validate its own settings. The settings for the [nautobot inventory](backend/nautobot.md) and for the [netbox inventory](backend/netbox.md) are available in their respective documentation.
+
+#### network section
+
+`global_delay_factor`, `banner_timeout` and `conn_timeout` have been removed from the network section, these settings can now be define within the `[network.netmiko_extras]` section. The idea is to provide a less opinionated system that can accept any extras parameters for Netmiko and/or Naplam.
+A similar section `[network.napalm_extras]` is available for napalm as well.
+
+If you want to conserve the same settings that were defined in 2.0, you can add this few lines to your configuration
+```toml
+[network.netmiko_extras]
+global_delay_factor = 15
+banner_timeout = 5
+conn_timeout = 5
+```
+
+#### netbox section
+
+The `[netbox]` section has been removed from the main configuration and has been slitted into 2 new sections in the configuration. `[inventory.settings]` for anything related to the inventory and `adapters.sot_settings` for anything related to the adapter itself, please check the configuration of the [netbox_api adapter](backend/netbox.md) for more information.
+
+#### adapters section
+- `network_params` has been renamed to `network_settings`
+- `sot_params` has been renamed to `sot_settings`
\ No newline at end of file
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..c421423b 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(settings, dict):
+                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..6e3340f1 100644
--- a/network_importer/cli.py
+++ b/network_importer/cli.py
@@ -27,6 +27,8 @@
 
 import network_importer.config as config
 from network_importer.main import NetworkImporter
+from network_importer.inventory import reachable_devs
+from network_importer.tasks import check_if_reachable
 
 import network_importer.performance as perf
 
@@ -46,7 +48,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()
 
     # ------------------------------------------------------------
@@ -170,16 +172,23 @@ def check(config_file, limit, debug, update_configs):
     help="limit the execution on a specific device or group of devices --limit=device1 or --limit='site=sitea' ",
     type=str,
 )
+@click.option("--check-connectivity", is_flag=True, help="Check if the devices are reachable on port 22")
 @click.option("--update-configs", is_flag=True, help="Pull the latest configs from the devices")
 @click.option(
     "--debug", is_flag=True, help="Keep the script in interactive mode once finished for troubleshooting", hidden=True
 )
 @main.command()
-def inventory(config_file, limit, debug, update_configs):
+def inventory(config_file, limit, debug, check_connectivity, update_configs):
     """Display inventory."""
     ni = init(config_file)
     ni.build_inventory(limit=limit)
 
+    if check_connectivity:
+        ni.nornir.filter(filter_func=reachable_devs).run(task=check_if_reachable, on_failed=True)
+
+    if update_configs:
+        ni.update_configurations()
+
     if limit:
         table = Table(title=f"Device Inventory (limit:{limit})")
     else:
@@ -192,12 +201,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 +214,9 @@ def inventory(config_file, limit, debug, update_configs):
     console = Console()
     console.print(table)
 
+    if debug:
+        pdb.set_trace()
+
 
 if __name__ == "__main__":
     main()
diff --git a/network_importer/config.py b/network_importer/config.py
index 9d940933..8e8a2885 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):
@@ -54,35 +53,12 @@ class Config:
 
         fields = {
             "address": {"env": "BATFISH_ADDRESS"},
+            "network_name": {"env": "BATFISH_NETWORK_NAME"},
+            "snapshot_name": {"env": "BATFISH_SNAPSHOT_NAME"},
             "api_key": {"env": "BATFISH_API_KEY"},
-        }
-
-
-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"},
+            "port_v1": {"env": "BATFISH_PORT_V1"},
+            "port_v2": {"env": "BATFISH_PORT_V2"},
+            "use_ssl": {"env": "BATFISH_USE_SSL"},
         }
 
 
@@ -92,9 +68,10 @@ class NetworkSettings(BaseSettings):
     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 +113,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 +126,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 +145,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 +159,6 @@ class Settings(BaseSettings):
     """
 
     main: MainSettings = MainSettings()
-    netbox: NetboxSettings = NetboxSettings()
     batfish: BatfishSettings = BatfishSettings()
     logs: LogsSettings = LogsSettings()
     network: NetworkSettings = NetworkSettings()
@@ -192,33 +167,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/converters.py b/network_importer/drivers/converters.py
index eb34c7ac..891b4b16 100644
--- a/network_importer/drivers/converters.py
+++ b/network_importer/drivers/converters.py
@@ -144,7 +144,7 @@ def convert_cisco_genie_vlans(device_name: str, data: dict) -> Vlans:
             continue
 
         if vlan_data.get("state", None) == "unsupport":
-            LOGGER.warning("%s | Unsupported VLAN found, VLAN %s", device_name, vid)
+            LOGGER.debug("%s | Unsupported VLAN found, VLAN %s", device_name, vid)
             continue
 
         results.vlans.append(Vlan(name=vlan_data["name"], vid=int(vlan_data["vlan_id"])))
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/network_importer/version.py b/network_importer/version.py
index b4ff3598..6c867dc7 100644
--- a/network_importer/version.py
+++ b/network_importer/version.py
@@ -1,3 +1,3 @@
 """Version number for network_importer."""
-__version_info__ = (2, 0, "0")
+__version_info__ = (3, 0, "0-dev")
 __version__ = ".".join([str(v) for v in __version_info__])
diff --git a/poetry.lock b/poetry.lock
index 52fe4232..d7b4376c 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"
@@ -170,7 +170,7 @@ d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
 
 [[package]]
 name = "certifi"
-version = "2020.12.5"
+version = "2021.5.30"
 description = "Python package for providing Mozilla's CA Bundle."
 category = "main"
 optional = false
@@ -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.2"
 description = "Genie libs sdk: Libraries containing all Triggers and Verifications"
 category = "main"
 optional = false
@@ -517,7 +517,7 @@ smmap = ">=3.0.1,<5"
 
 [[package]]
 name = "gitpython"
-version = "3.1.15"
+version = "3.1.17"
 description = "Python Git Library"
 category = "dev"
 optional = false
@@ -525,7 +525,7 @@ python-versions = ">=3.5"
 
 [package.dependencies]
 gitdb = ">=4.0.1,<5"
-typing-extensions = ">=3.7.4.0"
+typing-extensions = {version = ">=3.7.4.0", markers = "python_version < \"3.8\""}
 
 [[package]]
 name = "idna"
@@ -548,7 +548,7 @@ idna = ">=2.0"
 
 [[package]]
 name = "importlib-metadata"
-version = "4.0.1"
+version = "4.5.0"
 description = "Read metadata from Python packages"
 category = "main"
 optional = false
@@ -564,18 +564,18 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
 
 [[package]]
 name = "importlib-resources"
-version = "5.1.2"
+version = "5.1.4"
 description = "Read resources from Python packages"
 category = "main"
 optional = false
 python-versions = ">=3.6"
 
 [package.dependencies]
-zipp = {version = ">=0.4", markers = "python_version < \"3.8\""}
+zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
 
 [package.extras]
 docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"]
+testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"]
 
 [[package]]
 name = "invoke"
@@ -649,16 +649,16 @@ six = "*"
 
 [[package]]
 name = "junos-eznc"
-version = "2.5.4"
+version = "2.6.1"
 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 = "*"
@@ -709,7 +709,7 @@ python-versions = "*"
 
 [[package]]
 name = "more-itertools"
-version = "8.7.0"
+version = "8.8.0"
 description = "More routines for operating on iterables, beyond itertools"
 category = "dev"
 optional = false
@@ -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,41 +820,66 @@ 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 = "ntc-templates"
-version = "1.7.0"
-description = "Package to return structured data from the output of network devices."
+name = "nornir-napalm"
+version = "0.1.2"
+description = "NAPALM's plugins for nornir"
 category = "main"
 optional = false
-python-versions = "*"
+python-versions = ">=3.6,<4.0"
 
 [package.dependencies]
-textfsm = ">=1.1.0"
+napalm = ">=3,<4"
+nornir = ">=3,<4"
 
-[package.extras]
-dev = ["pytest", "pyyaml", "black", "yamllint", "ruamel.yaml"]
+[[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"
+version = "2.0.0"
+description = "TextFSM Templates for Network Devices, and Python wrapper for TextFSM's CliTable."
+category = "main"
+optional = false
+python-versions = ">=3.6,<4.0"
+
+[package.dependencies]
+textfsm = ">=1.1.0,<2.0.0"
 
 [[package]]
 name = "numpy"
@@ -1308,7 +1334,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 +1395,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
@@ -1377,14 +1403,14 @@ python-versions = ">=3.5"
 
 [[package]]
 name = "pylint"
-version = "2.8.2"
+version = "2.8.3"
 description = "python code static checker"
 category = "dev"
 optional = false
 python-versions = "~=3.6"
 
 [package.dependencies]
-astroid = ">=2.5.6,<2.7"
+astroid = "2.5.6"
 colorama = {version = "*", markers = "sys_platform == \"win32\""}
 isort = ">=4.2.5,<6"
 mccabe = ">=0.6,<0.7"
@@ -1414,6 +1440,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 +1610,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
 
 [[package]]
 name = "requests-mock"
-version = "1.9.1"
+version = "1.9.3"
 description = "Mock out responses from the requests package"
 category = "dev"
 optional = false
@@ -1657,7 +1697,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
@@ -1743,7 +1783,7 @@ six = "*"
 
 [[package]]
 name = "tftpy"
-version = "0.8.0"
+version = "0.8.1"
 description = "Python TFTP library"
 category = "main"
 optional = false
@@ -1759,7 +1799,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 
 [[package]]
 name = "tqdm"
-version = "4.60.0"
+version = "4.61.0"
 description = "Fast, Extensible Progress Meter"
 category = "main"
 optional = false
@@ -1795,7 +1835,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
@@ -1821,7 +1861,7 @@ robot = ["robotframework"]
 
 [[package]]
 name = "unicon.plugins"
-version = "21.4"
+version = "21.4.2"
 description = "Unicon Connection Library Plugins"
 category = "main"
 optional = false
@@ -1836,16 +1876,16 @@ dev = ["setuptools", "pip", "wheel", "coverage", "restview", "sphinx", "sphinxco
 
 [[package]]
 name = "urllib3"
-version = "1.26.4"
+version = "1.26.5"
 description = "HTTP library with thread-safe connection pooling, file post, and more."
 category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
 
 [package.extras]
+brotli = ["brotlipy (>=0.6.0)"]
 secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
 socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
-brotli = ["brotlipy (>=0.6.0)"]
 
 [[package]]
 name = "wcwidth"
@@ -1930,7 +1970,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 = "b4938edd486f12594a35e857400d945a03517d6cc977ae16cb4a4b235a3ffcda"
 
 [metadata.files]
 aiofiles = [
@@ -1995,8 +2035,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"},
@@ -2020,8 +2060,8 @@ black = [
     {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
 ]
 certifi = [
-    {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
-    {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
+    {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
+    {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
 ]
 cffi = [
     {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"},
@@ -2134,8 +2174,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 +2204,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,15 +2213,15 @@ 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.2-py3-none-any.whl", hash = "sha256:958a9af40f97c6e1c7992898c98342a6c0778e59b9adb55950425f4273513ad0"},
 ]
 gitdb = [
     {file = "gitdb-4.0.7-py3-none-any.whl", hash = "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0"},
     {file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"},
 ]
 gitpython = [
-    {file = "GitPython-3.1.15-py3-none-any.whl", hash = "sha256:a77824e516d3298b04fb36ec7845e92747df8fcfee9cacc32dd6239f9652f867"},
-    {file = "GitPython-3.1.15.tar.gz", hash = "sha256:05af150f47a5cca3f4b0af289b73aef8cf3c4fe2385015b06220cbcdee48bb6e"},
+    {file = "GitPython-3.1.17-py3-none-any.whl", hash = "sha256:29fe82050709760081f588dd50ce83504feddbebdc4da6956d02351552b1c135"},
+    {file = "GitPython-3.1.17.tar.gz", hash = "sha256:ee24bdc93dce357630764db659edaf6b8d664d4ff5447ccfeedd2dc5c253f41e"},
 ]
 idna = [
     {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
@@ -2191,12 +2231,12 @@ idna-ssl = [
     {file = "idna-ssl-1.1.0.tar.gz", hash = "sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c"},
 ]
 importlib-metadata = [
-    {file = "importlib_metadata-4.0.1-py3-none-any.whl", hash = "sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d"},
-    {file = "importlib_metadata-4.0.1.tar.gz", hash = "sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581"},
+    {file = "importlib_metadata-4.5.0-py3-none-any.whl", hash = "sha256:833b26fb89d5de469b24a390e9df088d4e52e4ba33b01dc5e0e4f41b81a16c00"},
+    {file = "importlib_metadata-4.5.0.tar.gz", hash = "sha256:b142cc1dd1342f31ff04bb7d022492b09920cb64fed867cd3ea6f80fe3ebd139"},
 ]
 importlib-resources = [
-    {file = "importlib_resources-5.1.2-py3-none-any.whl", hash = "sha256:ebab3efe74d83b04d6bf5cd9a17f0c5c93e60fb60f30c90f56265fce4682a469"},
-    {file = "importlib_resources-5.1.2.tar.gz", hash = "sha256:642586fc4740bd1cad7690f836b3321309402b20b332529f25617ff18e8e1370"},
+    {file = "importlib_resources-5.1.4-py3-none-any.whl", hash = "sha256:e962bff7440364183203d179d7ae9ad90cb1f2b74dcb84300e88ecc42dca3351"},
+    {file = "importlib_resources-5.1.4.tar.gz", hash = "sha256:54161657e8ffc76596c4ede7080ca68cb02962a2e074a2586b695a93a925d36e"},
 ]
 invoke = [
     {file = "invoke-1.5.0-py2-none-any.whl", hash = "sha256:da7c2d0be71be83ffd6337e078ef9643f41240024d6b2659e7b46e0b251e339f"},
@@ -2223,8 +2263,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.1.tar.gz", hash = "sha256:2f5de7dedaac8dd71bfea23c6a7d883e29947c91de1ba299a9242e0a4406ee46"},
+    {file = "junos_eznc-2.6.1-py2.py3-none-any.whl", hash = "sha256:f6345e3511086ef584e3c188e47d424d690af433dfe509082d507b9a8b19ec21"},
 ]
 lazy-object-proxy = [
     {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"},
@@ -2328,8 +2368,8 @@ mccabe = [
     {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
 ]
 more-itertools = [
-    {file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"},
-    {file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"},
+    {file = "more-itertools-8.8.0.tar.gz", hash = "sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a"},
+    {file = "more_itertools-8.8.0-py3-none-any.whl", hash = "sha256:2cf89ec599962f2ddc4d568a05defc40e0a587fbc10d5989713638864c36be4d"},
 ]
 multidict = [
     {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"},
@@ -2375,11 +2415,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,12 +2434,24 @@ 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"},
-    {file = "ntc_templates-1.7.0.tar.gz", hash = "sha256:4e098f83b35624751f4f6aaa4eed9eefd50a742f3d009c86f4bb22843a6a72fc"},
+    {file = "ntc_templates-2.0.0-py3-none-any.whl", hash = "sha256:6617f36aaa842179e94d8b8e6527e652baf4a18a5b2f94b26b6505e5722fbc95"},
+    {file = "ntc_templates-2.0.0.tar.gz", hash = "sha256:32d3b371dfe5aecd4c36f56184f109f1f75e4768e6087d234c0371cbefe82bcd"},
 ]
 numpy = [
     {file = "numpy-1.19.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc6bd4fd593cb261332568485e20a0712883cf631f6f5e8e86a52caa8b2b50ff"},
@@ -2739,28 +2791,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,12 +2829,12 @@ 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"},
-    {file = "pylint-2.8.2.tar.gz", hash = "sha256:586d8fa9b1891f4b725f587ef267abe2a1bad89d6b184520c7f07a253dd6e217"},
+    {file = "pylint-2.8.3-py3-none-any.whl", hash = "sha256:792b38ff30903884e4a9eab814ee3523731abd3c463f3ba48d7b627e87013484"},
+    {file = "pylint-2.8.3.tar.gz", hash = "sha256:0a049c5d47b629d9070c3932d13bff482b12119b6a241a93bc460b0be16953c8"},
 ]
 pyment = [
     {file = "Pyment-0.3.3-py2.py3-none-any.whl", hash = "sha256:a0c6ec59d06d24aeec3eaecb22115d0dc95d09e14209b2df838381fdf47a78cc"},
@@ -2806,6 +2858,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 +2973,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.3.tar.gz", hash = "sha256:8d72abe54546c1fc9696fa1516672f1031d72a55a1d66c85184f972a24ba0eba"},
+    {file = "requests_mock-1.9.3-py2.py3-none-any.whl", hash = "sha256:0a2d38a117c08bb78939ec163522976ad59a6b7fdd82b709e23bb98004a44970"},
 ]
 requests-toolbelt = [
     {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"},
@@ -3008,8 +3064,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"},
@@ -3038,15 +3094,15 @@ textfsm = [
     {file = "textfsm-1.1.0-py2.py3-none-any.whl", hash = "sha256:0aef3f9cad3d03905915fd62bff358c42b7dc35c863ff2cb0b5324c2b746cc24"},
 ]
 tftpy = [
-    {file = "tftpy-0.8.0.tar.gz", hash = "sha256:c9095f6420125690865717e251dac3382abe5562d98b79780857b4535f554ffe"},
+    {file = "tftpy-0.8.1.tar.gz", hash = "sha256:6bc1583d497d04162f05c79d27e836158cdc29f6ce275cc1f42f6f0f41965982"},
 ]
 toml = [
     {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
     {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
 ]
 tqdm = [
-    {file = "tqdm-4.60.0-py2.py3-none-any.whl", hash = "sha256:daec693491c52e9498632dfbe9ccfc4882a557f5fa08982db1b4d3adbe0887c3"},
-    {file = "tqdm-4.60.0.tar.gz", hash = "sha256:ebdebdb95e3477ceea267decfc0784859aa3df3e27e22d23b83e9b272bf157ae"},
+    {file = "tqdm-4.61.0-py2.py3-none-any.whl", hash = "sha256:736524215c690621b06fc89d0310a49822d75e599fcd0feb7cc742b98d692493"},
+    {file = "tqdm-4.61.0.tar.gz", hash = "sha256:cd5791b5d7c3f2f1819efc81d36eb719a38e0906a7380365c556779f585ea042"},
 ]
 transitions = [
     {file = "transitions-0.8.8-py2.py3-none-any.whl", hash = "sha256:f35efa070fbdf9a0f3f093b19f1258068786af75786a8cbcc884444f3d1a66d4"},
@@ -3085,9 +3141,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"},
@@ -3104,11 +3160,11 @@ unicon = [
     {file = "unicon-21.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:eee593dd32013c9db6c206377488d2eee187c9482c2cf4d024d337cf9d5c4cf3"},
 ]
 "unicon.plugins" = [
-    {file = "unicon.plugins-21.4-py3-none-any.whl", hash = "sha256:e0e2dc9e1acd43d7ca4d5a2889a55b8c81a819f418086018a87cd8c5443ef62a"},
+    {file = "unicon.plugins-21.4.2-py3-none-any.whl", hash = "sha256:d0174be3b87579ad871c60ffcca3114168ae2620bf778c333d8d9f49e369581b"},
 ]
 urllib3 = [
-    {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"},
-    {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"},
+    {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"},
+    {file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"},
 ]
 wcwidth = [
     {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
diff --git a/pyproject.toml b/pyproject.toml
index 1960560e..8f6dc66c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "network-importer"
-version = "2.0.0"
+version = "3.0.0-dev"
 description = "Network Importer tool to import an existing network into a Database / Source Of Truth"
 authors = ["Network to Code, LLC <opensource@networktocode.com>"]
 homepage = "https://github.com/networktocode/network-importer"
@@ -21,17 +21,21 @@ 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"
-genie = "^21.4"
-pyats = "^21.4"
+genie = "~21.4.0"
+pyats = "~21.4.0"
 netmiko = "^3.3"
-ntc-templates = "^1.6.0"
+ntc-templates = "^2.0"
 structlog = "^20.1.0"
 diffsync = "~1.2.0"
-rich = "^9.2.0"
+rich = "^9.2"
+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..9fb87bfc 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,86 @@ 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 -v $(pwd)/uwsgi.ini:/opt/nautobot/uwsgi.ini -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(45)
+    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.
diff --git a/uwsgi.ini b/uwsgi.ini
new file mode 100644
index 00000000..2d32a9c7
--- /dev/null
+++ b/uwsgi.ini
@@ -0,0 +1,45 @@
+[uwsgi]
+; The IP address (typically localhost) and port that the WSGI process should listen on
+http = 0.0.0.0:8000
+
+; Fail to start if any parameter in the configuration file isn’t explicitly understood by uWSGI
+strict = true
+
+; Enable master process to gracefully re-spawn and pre-fork workers
+master = true
+
+; Allow Python app-generated threads to run
+enable-threads = true
+
+;Try to remove all of the generated file/sockets during shutdown
+vacuum = true
+
+; Do not use multiple interpreters, allowing only Nautobot to run
+single-interpreter = true
+
+; Shutdown when receiving SIGTERM (default is respawn)
+die-on-term = true
+
+; Prevents uWSGI from starting if it is unable load Nautobot (usually due to errors)
+need-app = true
+
+; By default, uWSGI has rather verbose logging that can be noisy
+disable-logging = true
+
+; Assert that critical 4xx and 5xx errors are still logged
+log-4xx = true
+log-5xx = true
+
+;
+; Advanced settings (disabled by default)
+; Customize these for your environment if and only if you need them.
+; Ref: https://uwsgi-docs.readthedocs.io/en/latest/Options.html
+;
+
+; Number of uWSGI workers to spawn. This should typically be 2n+1, where n is the number of CPU cores present.
+; processes = 5
+
+; If using subdirectory hosting e.g. example.com/nautobot, you must uncomment this line. Otherwise you'll get double paths e.g. example.com/nautobot/nautobot/.
+; See: https://uwsgi-docs.readthedocs.io/en/latest/Changelog-2.0.11.html#fixpathinfo-routing-action
+; route-run = fixpathinfo:
+http-keepalive = 1
\ No newline at end of file


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/adapters/test_base_adapter.py b/tests/unit/adapters/test_base_adapter.py
new file mode 100644
index 00000000..096305a2
--- /dev/null
+++ b/tests/unit/adapters/test_base_adapter.py
@@ -0,0 +1,44 @@
+"""test for the base adapter."""
+from typing import List, Optional
+from pydantic import BaseSettings
+from network_importer.adapters.base import BaseAdapter
+
+
+def test_init_no_settings_class():
+
+    adapter = BaseAdapter(nornir="nornir_object", settings=None)
+    assert adapter.nornir == "nornir_object"
+    assert adapter.settings is None
+
+    adapter = BaseAdapter(nornir="nornir_object", settings={"mysettings": "settings"})
+    assert adapter.nornir == "nornir_object"
+    assert adapter.settings == {"mysettings": "settings"}
+
+
+def test_init_with_settings_class():
+    """Validate that the settings are properly initialized with the settings_class when present."""
+
+    class MyAdapterSettings(BaseSettings):
+        """Fake adapter settings."""
+
+        first: List[str] = list()
+        second: Optional[str]
+
+    class MyAdapter(BaseAdapter):
+        """test adapter."""
+
+        settings_class = MyAdapterSettings
+
+        def load(self):
+            """load must be defined."""
+
+    adapter = MyAdapter(nornir="nornir_object", settings=None)
+    assert adapter.nornir == "nornir_object"
+    assert adapter.settings is not None
+    assert adapter.settings.first == []
+    assert adapter.settings.second is None
+
+    adapter = MyAdapter(nornir="nornir_object", settings={"second": "settings"})
+    assert adapter.nornir == "nornir_object"
+    assert adapter.settings.first == []
+    assert adapter.settings.second == "settings"
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
 
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index 55440710..8d52b1da 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.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.
-"""
+"""Test utilities."""
 
 from network_importer.utils import (
     expand_vlans_list,