Repository: nautobot/nautobot-plugin-chatops-ansible (language: python)

Issue:
Update to NTC Dev Standards
I'm seeing a bunch of things that are not up to the dev standards. This needs to be addressed.


Discussion / hints:

Fix patch:
diff --git a/.bandit.yml b/.bandit.yml
index 6156fcd..b6d6baf 100644
--- a/.bandit.yml
+++ b/.bandit.yml
@@ -1,7 +1,5 @@
 ---
+skips: []
 # No need to check for security issues in the test scripts!
 exclude_dirs: 
-  - "nautobot_plugin_chatops_ansible/tests/"
-
-skips:
-  - "B404"
+  - "nautobot_chatops_extension_ansible/tests/"
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..ce35489
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,20 @@
+# Docker related
+development/Dockerfile
+development/docker-compose*.yml
+development/*.env
+*.env
+
+# Python
+**/*.pyc
+**/*.pyo
+
+
+# Other
+docs/_build
+FAQ.md
+.git/
+.gitignore
+.github
+tasks.py
+LICENSE
+**/*.log
diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..e3ba27d
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,4 @@
+[flake8]
+# E501: Line length is enforced by Black, so flake8 doesn't need to check it
+# W503: Black disagrees with this rule, as does PEP 8; Black wins
+ignore = E501, W503
diff --git a/.gitignore b/.gitignore
index 605ddb9..1907b40 100644
--- a/.gitignore
+++ b/.gitignore
@@ -171,6 +171,7 @@ ehthumbs_vista.db
 
 # Dump file
 *.stackdump
+dumps/
 
 # Folder config file
 [Dd]esktop.ini
diff --git a/.travis.yml b/.travis.yml
index 71a2b23..feeea6e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,26 +5,44 @@ python:
 - '3.7'
 - '3.8'
 env:
-# Each version of Nautobot listed here must have a corresponding directory/configuration file 
-# under development/nautobot_<NAUTOBOT_VER>/configuration.py
   matrix:
-  - NAUTOBOT_VER=v1.0.0b3
-  - NAUTOBOT_VER=main
-# Encrypted value for PYPI_TOKEN, this secret has been generated with the following command
-#   travis encrypt PYPI_TOKEN=<value> --add env.global --com
-# Might need to update it once the repo is publish (travis-ci.org vs travis-ci.com)
-  # global:
-  #   secure: <Add encrypted token>
+  - INVOKE_NAUTOBOT_CHATOPS_EXTENSION_ANSIBLE_NAUTOBOT_VER=1.0.2
+# Add your encrypted secret below, you can encrypt secret using "travis encrypt"
+# https://docs.travis-ci.com/user/environment-variables/#defining-encrypted-variables-in-travisyml
+# global:
+#   secure: <Add encrypted token>
 services:
 - docker
 # --------------------------------------------------------------------------
 # Tests
 # --------------------------------------------------------------------------
 before_script:
-- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
-- pip install invoke docker-compose
-- cp creds.env.example creds.env
+  # When we go public, the following line might need removed.
+  - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
+  - pip install invoke docker-compose
+  - "curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py > /tmp/get-poetry.py"
+  - "python /tmp/get-poetry.py -y --version 1.1.6"
+  - "source $HOME/.poetry/env"
+  - "cp development/creds.example.env development/creds.env"
 
 script:
-- invoke build --python-ver $TRAVIS_PYTHON_VERSION
-- invoke tests --python-ver $TRAVIS_PYTHON_VERSION
+  # If you want to test different versions, it may require updating the poetry definition for Nautobot
+  # as that is where the version is controlled
+  # - "poetry add nautobot=$NAUTOBOT_VER"
+  - "INVOKE_NAUTOBOT_CHATOPS_EXTENSION_ANSIBLE_PYTHON_VER=$TRAVIS_PYTHON_VERSION invoke build --no-cache"
+  - "INVOKE_NAUTOBOT_CHATOPS_EXTENSION_ANSIBLE_PYTHON_VER=$TRAVIS_PYTHON_VERSION invoke unittest --failfast"
+# --------------------------------------------------------------------------
+# Deploy
+#  Uncomment the section below if you would like to publish a new release to pypi automatically
+#  when a new tag is created in master. You"ll also need to generate a dedicated API key for this project in pypi
+#  and encrypt the key with "travis encrypt PYPI_TOKEN=<value> --add env.global --com"
+# --------------------------------------------------------------------------
+# deploy:
+#   provider: script
+#   script: poetry config pypi-token.pypi $PYPI_TOKEN && poetry publish --build
+#   skip_cleanup: true
+#   on:
+#     tags: true
+#     branch: master
+#     condition: $NAUTOBOT_VER = master
+#     python: 3.7
\ No newline at end of file
diff --git a/.yamllint.yml b/.yamllint.yml
index 442b436..58324ed 100644
--- a/.yamllint.yml
+++ b/.yamllint.yml
@@ -1,9 +1,10 @@
 ---
-extends: default
-
+extends: "default"
 rules:
-  truthy: disable
-  brackets: { min-spaces-inside: 0, max-spaces-inside: 1 }
-  braces: { min-spaces-inside: 0, max-spaces-inside: 1 }
-  line-length: { allow-non-breakable-inline-mappings: true, allow-non-breakable-words: true, max: 180 }
-  comments-indentation: disable
+  comments: "enable"
+  empty-values: "enable"
+  indentation:
+    indent-sequences: "consistent"
+  line-length: "disable"
+  quoted-strings:
+    quote-type: "double"
diff --git a/creds.env.example b/creds.env.example
deleted file mode 100644
index 54bf5d8..0000000
--- a/creds.env.example
+++ /dev/null
@@ -1,11 +0,0 @@
-# nautobot_chatops
-SLACK_API_TOKEN=xoxb-...
-SLACK_SIGNING_SECRET=...
-MICROSOFT_APP_ID=...
-MICROSOFT_APP_PASSWORD=...
-WEBEX_TEAMS_ACCESS_TOKEN=...
-WEBEX_TEAMS_SIGNING_SECRET=...
-# nautobot_plugin_chatops_ansible
-TOWER_URI=https://...
-TOWER_USERNAME=...
-TOWER_PASSWORD=...
diff --git a/development/Dockerfile b/development/Dockerfile
index f01acf5..1dac3a2 100644
--- a/development/Dockerfile
+++ b/development/Dockerfile
@@ -1,29 +1,32 @@
-ARG python_ver=3.7
-FROM python:${python_ver}
+ARG NAUTOBOT_VER="1.0.1"
+ARG PYTHON_VER=3.8
+FROM ghcr.io/nautobot/nautobot-dev:${NAUTOBOT_VER}-py${PYTHON_VER}
 
-ARG nautobot_ver=main
-ENV PYTHONUNBUFFERED 1
+ENV prometheus_multiproc_dir=/prom_cache
 
-RUN mkdir -p /opt
+ARG NAUTOBOT_ROOT=/opt/nautobot
 
-RUN pip install --upgrade pip\
-  && pip install poetry
+ENV NAUTOBOT_ROOT ${NAUTOBOT_ROOT}
 
-# -------------------------------------------------------------------------------------
-# Install Nautobot
-# -------------------------------------------------------------------------------------
-RUN git clone --single-branch --branch ${nautobot_ver} https://github.com/nautobot/nautobot.git /opt/nautobot/ && \
-    cd /opt/nautobot/ && \
-    poetry config virtualenvs.create false && poetry install
+WORKDIR $NAUTOBOT_ROOT
+
+# Configure poetry
+RUN poetry config virtualenvs.create false \
+    && poetry config installer.parallel false
 
 # -------------------------------------------------------------------------------------
 # Install Nautobot Plugin
 # -------------------------------------------------------------------------------------
 WORKDIR /source
+
+# Copy in only pyproject.toml/poetry.lock to help with caching this layer if no updates to dependencies
+COPY poetry.lock pyproject.toml /source/
+# --no-root declares not to install the project package since we're wanting to take advantage of caching dependency installation
+# and the project is copied in and installed after this step
+RUN poetry install --no-interaction --no-ansi --no-root
+
+# Copy in the rest of the source code and install local Nautobot plugin
 COPY . /source
-RUN mkdir -p /root/.nautobot && \
-    cat ./development/base_configuration.py >> /root/.nautobot/nautobot_config.py && \
-    cat ./development/nautobot_${nautobot_ver}/configuration.py >> /root/.nautobot/nautobot_config.py
 RUN poetry install --no-interaction --no-ansi
 
-WORKDIR /opt/nautobot/
+COPY development/nautobot_config.py ${NAUTOBOT_ROOT}/nautobot_config.py
diff --git a/development/base_configuration.py b/development/base_configuration.py
deleted file mode 100644
index bb22817..0000000
--- a/development/base_configuration.py
+++ /dev/null
@@ -1,309 +0,0 @@
-"""Nautobot configuration file."""
-# pylint: skip-file
-import os
-
-from nautobot.core.settings import *  # noqa F401,F403
-
-# This is a list of valid fully-qualified domain names (FQDNs) for the Nautobot server. Nautobot will not permit write
-# access to the server via any other hostnames. The first FQDN in the list will be treated as the preferred name.
-#
-# Example: ALLOWED_HOSTS = ['nautobot.example.com', 'nautobot.internal.local']
-ALLOWED_HOSTS = ["*"]
-
-# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters:
-#   https://docs.djangoproject.com/en/stable/ref/settings/#databases
-DATABASES = {
-    "default": {
-        "NAME": os.getenv("POSTGRES_DB", "nautobot"),  # Database name
-        "USER": os.getenv("POSTGRES_USER", ""),  # Database username
-        "PASSWORD": os.getenv("POSTGRES_PASSWORD", ""),  # Datbase password
-        "HOST": os.getenv("POSTGRES_HOST", "localhost"),  # Database server
-        "PORT": os.getenv("NAUTOBOT_DB_PORT", ""),  # Database port (leave blank for default)
-        "CONN_MAX_AGE": os.getenv("NAUTOBOT_DB_TIMEOUT", 300),  # Database timeout
-        "ENGINE": "django.db.backends.postgresql",  # Database driver (Postgres only supported!)
-    }
-}
-
-# Nautobot uses RQ for task scheduling. These are the following defaults.
-# For detailed configuration see: https://github.com/rq/django-rq#installation
-RQ_QUEUES = {
-    "default": {
-        "HOST": "localhost",
-        "PORT": 6379,
-        "DB": 0,
-        "PASSWORD": "",
-        "SSL": False,
-        "DEFAULT_TIMEOUT": 300,
-    },  # nosec
-    # "with-sentinel": {
-    #     "SENTINELS": [
-    #         ("mysentinel.redis.example.com", 6379)
-    #         ("othersentinel.redis.example.com", 6379)
-    #     ],
-    #     "MASTER_NAME": 'nautobot",
-    #     "DB": 0,
-    #     "PASSWORD": "",
-    #     "SOCKET_TIMEOUT": None,
-    #     'CONNECTION_KWARGS': {
-    #         'socket_connect_timeout': 10,
-    #     },
-    # },
-    "webhooks": {
-        "HOST": "localhost",
-        "PORT": 6379,
-        "DB": 0,
-        "PASSWORD": "",
-        "SSL": False,
-        "DEFAULT_TIMEOUT": 300,
-    },
-    "check_releases": {
-        "HOST": "localhost",
-        "PORT": 6379,
-        "DB": 0,
-        "PASSWORD": "",  # nosec
-        "SSL": False,
-        "DEFAULT_TIMEOUT": 300,
-    },
-    "custom_fields": {
-        "HOST": "localhost",
-        "PORT": 6379,
-        "DB": 0,
-        "PASSWORD": "",  # nosec
-        "SSL": False,
-        "DEFAULT_TIMEOUT": 900,
-    },
-}
-
-# Nautobot uses Cacheops for database query caching. These are the following defaults.
-# For detailed configuration see: https://github.com/Suor/django-cacheops#setup
-CACHEOPS_REDIS = "redis://{redis_host}:{redis_port}/1".format(
-    redis_host=os.getenv("REDIS_HOST"), redis_port=os.getenv("REDIS_PORT")
-)
-
-# This key is used for secure generation of random numbers and strings. It must never be exposed outside of this file.
-# For optimal security, SECRET_KEY should be at least 50 characters in length and contain a mix of letters, numbers, and
-# symbols. Nautobot will not run without this defined. For more information, see
-# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY
-SECRET_KEY = os.environ.get("SECRET_KEY", "")
-
-
-#########################
-#                       #
-#   Optional settings   #
-#                       #
-#########################
-
-# Specify one or more name and email address tuples representing Nautobot administrators. These people will be notified of
-# application errors (assuming correct email settings are provided).
-ADMINS = [
-    # ['John Doe', 'jdoe@example.com'],
-]
-
-# URL schemes that are allowed within links in Nautobot
-ALLOWED_URL_SCHEMES = (
-    "file",
-    "ftp",
-    "ftps",
-    "http",
-    "https",
-    "irc",
-    "mailto",
-    "sftp",
-    "ssh",
-    "tel",
-    "telnet",
-    "tftp",
-    "vnc",
-    "xmpp",
-)
-
-# Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same
-# content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP.
-BANNER_TOP = ""
-BANNER_BOTTOM = ""
-
-# Text to include on the login page above the login form. HTML is allowed.
-BANNER_LOGIN = ""
-
-# Cache timeout in seconds. Cannot be 0. Defaults to 900 (15 minutes). To disable caching, set CACHEOPS_ENABLED to False
-CACHEOPS_DEFAULTS = {"timeout": 900}
-
-# Set to False to disable caching with cacheops. (Default: True)
-CACHEOPS_ENABLED = True
-
-# Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90)
-CHANGELOG_RETENTION = 90
-
-# If True, all origins will be allowed. Other settings restricting allowed origins will be ignored.
-# Defaults to False. Setting this to True can be dangerous, as it allows any website to make
-# cross-origin requests to yours. Generally you'll want to restrict the list of allowed origins with
-# CORS_ALLOWED_ORIGINS or CORS_ALLOWED_ORIGIN_REGEXES.
-CORS_ALLOW_ALL_ORIGINS = False
-
-# A list of origins that are authorized to make cross-site HTTP requests. Defaults to [].
-CORS_ALLOWED_ORIGINS = [
-    # 'https://hostname.example.com',
-]
-
-# A list of strings representing regexes that match Origins that are authorized to make cross-site
-# HTTP requests. Defaults to [].
-CORS_ALLOWED_ORIGIN_REGEXES = [
-    # r'^(https?://)?(\w+\.)?example\.com$',
-]
-
-# FQDNs that are considered trusted origins for secure, cross-domain, requests such as HTTPS POST.
-# If running Nautobot under a single domain, you may not need to set this variable;
-# if running on multiple domains, you *may* need to set this variable to more or less the same as ALLOWED_HOSTS above.
-# https://docs.djangoproject.com/en/stable/ref/settings/#csrf-trusted-origins
-CSRF_TRUSTED_ORIGINS = []
-
-# Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal
-# sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging
-# on a production system.
-DEBUG = False
-
-# Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce unique IP space
-# within the global table (all prefixes and IP addresses not assigned to a VRF), set
-# ENFORCE_GLOBAL_UNIQUE to True.
-ENFORCE_GLOBAL_UNIQUE = False
-
-# Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and
-# by anonymous users. List models in the form `<app>.<model>`. Add '*' to this list to exempt all models.
-EXEMPT_VIEW_PERMISSIONS = [
-    # 'dcim.site',
-    # 'dcim.region',
-    # 'ipam.prefix',
-]
-
-# Global 3rd-party authentication settings
-EXTERNAL_AUTH_DEFAULT_GROUPS = []
-EXTERNAL_AUTH_DEFAULT_PERMISSIONS = {}
-
-# If hosting Nautobot in a subdirectory, you must set this value to match the base URL prefix configured in your HTTP server (e.g. `/nautobot/`). When not set, URLs will default to being prefixed by `/`.
-FORCE_SCRIPT_NAME = None
-
-# HTTP proxies Nautobot should use when sending outbound HTTP requests (e.g. for webhooks).
-# HTTP_PROXIES = {
-#     'http': 'http://10.10.1.10:3128',
-#     'https': 'http://10.10.1.10:1080',
-# }
-
-# IP addresses recognized as internal to the system. The debugging toolbar will be available only to clients accessing
-# Nautobot from an internal IP.
-INTERNAL_IPS = ("127.0.0.1", "::1")
-
-# Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs:
-#   https://docs.djangoproject.com/en/stable/topics/logging/
-LOGGING = {}
-
-# Setting this to True will display a "maintenance mode" banner at the top of every page.
-MAINTENANCE_MODE = False
-
-# An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g.
-# "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request
-# all objects by specifying "?limit=0".
-MAX_PAGE_SIZE = 1000
-
-# The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that
-# the default value of this setting is within the invoking user's home directory
-# MEDIA_ROOT = os.path.expanduser('~/.nautobot/media')
-
-# By default uploaded media is stored on the local filesystem. Using Django-storages is also supported. Provide the
-# class path of the storage driver in STORAGE_BACKEND and any configuration options in STORAGE_CONFIG. For example:
-# STORAGE_BACKEND = 'storages.backends.s3boto3.S3Boto3Storage'
-# STORAGE_CONFIG = {
-#     'AWS_ACCESS_KEY_ID': 'Key ID',
-#     'AWS_SECRET_ACCESS_KEY': 'Secret',
-#     'AWS_STORAGE_BUCKET_NAME': 'nautobot',
-#     'AWS_S3_REGION_NAME': 'eu-west-1',
-# }
-
-# Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics'
-METRICS_ENABLED = False
-
-# Credentials that Nautobot will uses to authenticate to devices when connecting via NAPALM.
-NAPALM_USERNAME = ""
-NAPALM_PASSWORD = ""  # nosec
-
-# NAPALM timeout (in seconds). (Default: 30)
-NAPALM_TIMEOUT = 30
-
-# NAPALM optional arguments (see https://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must
-# be provided as a dictionary.
-NAPALM_ARGS = {}
-
-# Determine how many objects to display per page within a list. (Default: 50)
-PAGINATE_COUNT = 50
-
-# Enable installed plugins. Add the name of each plugin to the list.
-PLUGINS = ["nautobot_chatops", "nautobot_plugin_chatops_ansible"]
-
-# Plugins configuration settings. These settings are used by various plugins that the user may have installed.
-# Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings.
-PLUGINS_CONFIG = {
-    "nautobot_chatops": {
-        "enable_slack": True,
-        "slack_api_token": os.environ.get("SLACK_API_TOKEN"),
-        "slack_signing_secret": os.environ.get("SLACK_SIGNING_SECRET"),
-        "slack_slash_command_prefix": os.environ.get("SLACK_SLASH_COMMAND_PREFIX", "/"),
-        "enable_ms_teams": True,
-        "microsoft_app_id": os.environ.get("MICROSOFT_APP_ID"),
-        "microsoft_app_password": os.environ.get("MICROSOFT_APP_PASSWORD"),
-        "enable_webex_teams": True,
-        "webex_teams_token": os.environ.get("WEBEX_TEAMS_ACCESS_TOKEN"),
-        "webex_teams_signing_secret": os.environ.get("WEBEX_TEAMS_SIGNING_SECRET"),
-    },
-    "nautobot_plugin_chatops_ansible": {
-        "tower_uri": os.environ.get("TOWER_URI"),
-        "tower_username": os.environ.get("TOWER_USERNAME"),
-        "tower_password": os.environ.get("TOWER_PASSWORD"),
-    },
-}
-
-# When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to
-# prefer IPv4 instead.
-PREFER_IPV4 = False
-
-# Rack elevation size defaults, in pixels. For best results, the ratio of width to height should be roughly 10:1.
-RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = 22
-RACK_ELEVATION_DEFAULT_UNIT_WIDTH = 220
-
-# Remote auth backend settings
-REMOTE_AUTH_AUTO_CREATE_USER = False
-REMOTE_AUTH_HEADER = "HTTP_REMOTE_USER"
-
-# This determines how often the GitHub API is called to check the latest release of Nautobot. Must be at least 1 hour.
-RELEASE_CHECK_TIMEOUT = 24 * 3600
-
-# This repository is used to check whether there is a new release of Nautobot available. Set to None to disable the
-# version check or use the URL below to check for release in the official Nautobot repository.
-RELEASE_CHECK_URL = None
-# RELEASE_CHECK_URL = 'https://api.github.com/repos/nautobot/nautobot/releases'
-
-# The length of time (in seconds) for which a user will remain logged into the web UI before being prompted to
-# re-authenticate. (Default: 1209600 [14 days])
-SESSION_COOKIE_AGE = 1209600  # 2 weeks, in seconds
-
-# By default, Nautobot will store session data in the database. Alternatively, a file path can be specified here to use
-# local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only
-# database access.) Note that the user as which Nautobot runs must have read and write permissions to this path.
-SESSION_FILE_PATH = None
-
-# Configure SSO, for more information see docs/configuration/authentication/sso.md
-SOCIAL_AUTH_POSTGRES_JSONFIELD = False
-
-# Time zone (default: UTC)
-TIME_ZONE = "UTC"
-
-# Date/time formatting. See the following link for supported formats:
-# https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date
-DATE_FORMAT = "N j, Y"
-SHORT_DATE_FORMAT = "Y-m-d"
-TIME_FORMAT = "g:i a"
-SHORT_TIME_FORMAT = "H:i:s"
-DATETIME_FORMAT = "N j, Y g:i a"
-SHORT_DATETIME_FORMAT = "Y-m-d H:i"
-
-# A list of strings designating all applications that are enabled in this Django installation. Each string should be a dotted Python path to an application configuration class (preferred), or a package containing an application.
-# https://nautobot.readthedocs.io/en/latest/configuration/optional-settings/#extra-applications
-EXTRA_INSTALLED_APPS = []
diff --git a/development/creds.example.env b/development/creds.example.env
new file mode 100644
index 0000000..9ebd88b
--- /dev/null
+++ b/development/creds.example.env
@@ -0,0 +1,20 @@
+POSTGRES_PASSWORD=notverysecurepwd
+REDIS_PASSWORD=notverysecurepwd
+SECRET_KEY=r8OwDznj!!dci#P9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj
+NAUTOBOT_CREATE_SUPERUSER=true
+NAUTOBOT_SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
+NAUTOBOT_SUPERUSER_PASSWORD=admin
+# POSTGRES_HOST=localhost
+# REDIS_HOST=localhost
+# NAUTOBOT_ROOT=./development
+# nautobot_chatops
+SLACK_API_TOKEN=xoxb-...
+SLACK_SIGNING_SECRET=...
+MICROSOFT_APP_ID=...
+MICROSOFT_APP_PASSWORD=...
+WEBEX_TEAMS_ACCESS_TOKEN=...
+WEBEX_TEAMS_SIGNING_SECRET=...
+# nautobot_chatops_extension_ansible
+NAUTOBOT_TOWER_URI=https://...
+NAUTOBOT_TOWER_USERNAME=...
+NAUTOBOT_TOWER_PASSWORD=...
diff --git a/development/dev.env b/development/dev.env
index d86bf59..fc71b03 100644
--- a/development/dev.env
+++ b/development/dev.env
@@ -1,14 +1,18 @@
 ALLOWED_HOSTS=*
-NAPALM_TIMEOUT=5
+BANNER_TOP="Local"
+CHANGELOG_RETENTION=0
+DEBUG=True
 MAX_PAGE_SIZE=0
-SECRET_KEY=bqn8nn4qmjvx4hv2u5qr4pp46s3w9skbb63y
-POSTGRES_HOST=postgres
-POSTGRES_USER=nautobot
-POSTGRES_PASSWORD=decinablesprewad
+METRICS_ENABLED=True
+NAPALM_TIMEOUT=5
+NAUTOBOT_ROOT=/opt/nautobot
 POSTGRES_DB=nautobot
-CHANGELOG_RETENTION=0
+POSTGRES_HOST=postgres
+POSTGRES_USER=nautbot
 REDIS_HOST=redis
 REDIS_PORT=6379
-# Uncomment REDIS_SSL if using SSL
 # REDIS_SSL=True
-REDIS_PASSWORD=decinablesprewad
+# Uncomment REDIS_SSL if using SSL
+SUPERUSER_EMAIL=admin@example.com
+SUPERUSER_NAME=admin
+SECRET_KEY=bqn8nn4qmjvx4hv2u5qr4pp46s3w9skbb63y
\ No newline at end of file
diff --git a/development/docker-compose.base.yml b/development/docker-compose.base.yml
new file mode 100644
index 0000000..e292392
--- /dev/null
+++ b/development/docker-compose.base.yml
@@ -0,0 +1,32 @@
+---
+x-nautobot-build: &nautobot-build
+  build:
+    args:
+      NAUTOBOT_VER: "${NAUTOBOT_VER}"
+      PYTHON_VER: "${PYTHON_VER}"
+    context: "../"
+    dockerfile: "development/Dockerfile"
+x-nautobot-base: &nautobot-base
+  image: "nautobot-chatops-extension-ansible/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}"
+  env_file:
+    - "dev.env"
+    - "creds.env"
+  tty: true
+
+version: "3.4"
+services:
+  nautobot:
+    ports:
+      - "8080:8080"
+    depends_on:
+      - "postgres"
+      - "redis"
+    <<: *nautobot-build
+    <<: *nautobot-base
+  worker:
+    entrypoint: "nautobot-server rqworker"
+    depends_on:
+      - "nautobot"
+    healthcheck:
+      disable: true
+    <<: *nautobot-base
diff --git a/development/docker-compose.dev.yml b/development/docker-compose.dev.yml
new file mode 100644
index 0000000..02e81f6
--- /dev/null
+++ b/development/docker-compose.dev.yml
@@ -0,0 +1,16 @@
+# We can't remove volumes in a compose override, for the test configuration using the final containers
+# we don't want the volumes so this is the default override file to add the volumes in the dev case
+# any override will need to include these volumes to use them.
+# see:  https://github.com/docker/compose/issues/3729
+---
+version: "3.4"
+services:
+  nautobot:
+    command: "nautobot-server runserver 0.0.0.0:8080"
+    volumes:
+      - "./nautobot_config.py:/opt/nautobot/nautobot_config.py"
+      - "../:/source"
+  worker:
+    volumes:
+      - "./nautobot_config.py:/opt/nautobot/nautobot_config.py"
+      - "../:/source"
diff --git a/development/docker-compose.docs.yml b/development/docker-compose.docs.yml
new file mode 100644
index 0000000..d22038d
--- /dev/null
+++ b/development/docker-compose.docs.yml
@@ -0,0 +1,11 @@
+---
+version: "3.4"
+services:
+  docs:
+    image: "nautobot-serializer/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}"
+    entrypoint: "mkdocs serve -v -a 0.0.0.0:8080"
+    volumes:
+      - "../docs:/source/docs:ro"
+      - "../mkdocs.yml:/source/mkdocs.yml:ro"
+    ports:
+      - "8001:8080"
diff --git a/development/docker-compose.requirements.yml b/development/docker-compose.requirements.yml
new file mode 100644
index 0000000..175cd29
--- /dev/null
+++ b/development/docker-compose.requirements.yml
@@ -0,0 +1,25 @@
+---
+version: "3.4"
+services:
+  postgres:
+    image: "postgres:13-alpine"
+    env_file:
+      - "dev.env"
+      - "creds.env"
+    volumes:
+      - "postgres_data:/var/lib/postgresql/data"
+    ports:
+      - "5432:5432"
+  redis:
+    image: "redis:6-alpine"
+    command:
+      - "sh"
+      - "-c"  # this is to evaluate the $REDIS_PASSWORD from the env
+      - "redis-server --appendonly yes --requirepass $$REDIS_PASSWORD"
+    env_file:
+      - "dev.env"
+      - "creds.env"
+    ports:
+      - "6379:6379"
+volumes:
+  postgres_data: {}
diff --git a/development/docker-compose.yml b/development/docker-compose.yml
deleted file mode 100644
index f42326d..0000000
--- a/development/docker-compose.yml
+++ /dev/null
@@ -1,51 +0,0 @@
----
-version: '3'
-services:
-  nautobot:
-    build:
-      context: ../
-      dockerfile: development/Dockerfile
-    image: "nautobot-plugin-chatops-ansible/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}"
-    command: >
-      sh -c "nautobot-server migrate &&
-             nautobot-server collectstatic &&
-             nautobot-server runserver 0.0.0.0:8000"
-    ports:
-      - "8000:8000"
-    depends_on:
-      - postgres
-      - redis
-    env_file:
-      - ./dev.env
-      - ../creds.env.example
-    volumes:
-      - ../nautobot_plugin_chatops_ansible:/source/nautobot_plugin_chatops_ansible
-    tty: true
-  worker:
-    build:
-      context: ../
-      dockerfile: development/Dockerfile
-    image: "nautobot-plugin-chatops-ansible/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}"
-    command: sh -c "nautobot-server rqworker"
-    depends_on:
-      - nautobot
-    env_file:
-      - ./dev.env
-      - ../creds.env.example
-    volumes:
-      - ../nautobot_plugin_chatops_ansible:/source/nautobot_plugin_chatops_ansible
-    tty: true
-  postgres:
-    image: postgres:10
-    env_file: dev.env
-    volumes:
-      - pgdata_nautobot_plugin_chatops_ansible:/var/lib/postgresql/data
-  redis:
-    image: redis:5-alpine
-    command:
-      - sh
-      - -c  # this is to evaluate the $REDIS_PASSWORD from the env
-      - redis-server --appendonly yes --requirepass $$REDIS_PASSWORD  ## $$ because of docker-compose
-    env_file: ./dev.env
-volumes:
-  pgdata_nautobot_plugin_chatops_ansible:
diff --git a/development/nautobot_config.py b/development/nautobot_config.py
new file mode 100644
index 0000000..006f095
--- /dev/null
+++ b/development/nautobot_config.py
@@ -0,0 +1,279 @@
+"""Nautobot configuration."""
+import os
+import sys
+
+from distutils.util import strtobool
+
+from django.core.exceptions import ImproperlyConfigured
+from nautobot.core.settings import *  # noqa: F401,F403 pylint: disable=wildcard-import,unused-wildcard-import
+
+# pylint: disable=line-too-long
+
+
+def is_truthy(arg):
+    """Convert "truthy" strings into Booleans.
+
+    Examples:
+        >>> is_truthy('yes')
+        True
+    Args:
+        arg (str): Truthy string (True values are y, yes, t, true, on and 1; false values are n, no,
+        f, false, off and 0. Raises ValueError if val is anything else.
+    """
+    if isinstance(arg, bool):
+        return arg
+    return bool(strtobool(arg))
+
+
+# Enforce required configuration parameters
+for key in [
+    "ALLOWED_HOSTS",
+    "POSTGRES_DB",
+    "POSTGRES_USER",
+    "POSTGRES_HOST",
+    "POSTGRES_PASSWORD",
+    "REDIS_HOST",
+    "REDIS_PASSWORD",
+    "SECRET_KEY",
+]:
+    if not os.environ.get(key):
+        raise ImproperlyConfigured(f"Required environment variable {key} is missing.")
+
+# For reference see https://nautobot.readthedocs.io/en/latest/configuration/required-settings/
+# Based on config from nautobot-server init
+
+#########################
+#                       #
+#   Required settings   #
+#                       #
+#########################
+
+# This is a list of valid fully-qualified domain names (FQDNs) for the Nautobot server. Nautobot will not permit write
+# access to the server via any other hostnames. The first FQDN in the list will be treated as the preferred name.
+#
+# Example: ALLOWED_HOSTS = ['nautobot.example.com', 'nautobot.internal.local']
+ALLOWED_HOSTS = os.environ["ALLOWED_HOSTS"].split(" ")
+
+# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters:
+#   https://docs.djangoproject.com/en/stable/ref/settings/#databases
+DATABASE_PORT = 5432 if not os.environ.get("POSTGRES_PORT", False) else int(os.environ["POSTGRES_PORT"])
+DATABASES = {
+    "default": {
+        "NAME": os.getenv("POSTGRES_DB", ""),  # Database name
+        "USER": os.getenv("POSTGRES_USER", ""),  # Database username
+        "PASSWORD": os.getenv("POSTGRES_PASSWORD", ""),  # Datbase password
+        "HOST": os.getenv("POSTGRES_HOST", "localhost"),  # Database server
+        "PORT": DATABASE_PORT,  # Database port (leave blank for default)
+        "CONN_MAX_AGE": int(os.environ.get("NAUTOBOT_DB_TIMEOUT", 300)),  # Database timeout
+        "ENGINE": "django.db.backends.postgresql",  # Database driver (Postres only supported!)
+    }
+}
+
+# Redis variables
+REDIS_HOST = os.getenv("REDIS_HOST", "localhost")
+REDIS_PORT = os.getenv("REDIS_PORT", "6379")
+REDIS_PASSWORD = os.getenv("REDIS_PASSWORD", "")
+
+# Check for Redis SSL
+REDIS_SCHEME = "redis"
+REDIS_SSL = is_truthy(os.getenv("REDIS_SSL", "False"))
+if REDIS_SSL:
+    REDIS_SCHEME = "rediss"
+
+# The django-redis cache is used to establish concurrent locks using Redis. The
+# django-rq settings will use the same instance/database by default.
+#
+# This "default" server is now used by RQ_QUEUES.
+# >> See: nautobot.core.settings.RQ_QUEUES
+CACHES = {
+    "default": {
+        "BACKEND": "django_redis.cache.RedisCache",
+        "LOCATION": f"{REDIS_SCHEME}://{REDIS_HOST}:{REDIS_PORT}/0",
+        "TIMEOUT": 300,
+        "OPTIONS": {
+            "CLIENT_CLASS": "django_redis.client.DefaultClient",
+            "PASSWORD": REDIS_PASSWORD,
+        },
+    }
+}
+
+# REDIS CACHEOPS
+CACHEOPS_REDIS = f"{REDIS_SCHEME}://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/1"
+
+# This key is used for secure generation of random numbers and strings. It must never be exposed outside of this file.
+# For optimal security, SECRET_KEY should be at least 50 characters in length and contain a mix of letters, numbers, and
+# symbols. Nautobot will not run without this defined. For more information, see
+# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY
+SECRET_KEY = os.environ["SECRET_KEY"]
+
+
+#########################
+#                       #
+#   Optional settings   #
+#                       #
+#########################
+
+# Specify one or more name and email address tuples representing Nautobot administrators. These people will be notified of
+# application errors (assuming correct email settings are provided).
+ADMINS = [
+    # ['John Doe', 'jdoe@example.com'],
+]
+
+# Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same
+# content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP.
+BANNER_TOP = os.environ.get("BANNER_TOP", "")
+BANNER_BOTTOM = os.environ.get("BANNER_BOTTOM", "")
+
+# Text to include on the login page above the login form. HTML is allowed.
+BANNER_LOGIN = os.environ.get("BANNER_LOGIN", "")
+
+# Base URL path if accessing Nautobot within a directory. For example, if installed at https://example.com/nautobot/, set:
+# BASE_PATH = 'nautobot/'
+BASE_PATH = os.environ.get("BASE_PATH", "")
+
+# Cache timeout in seconds. Set to 0 to dissable caching. Defaults to 900 (15 minutes)
+CACHEOPS_DEFAULTS = {"timeout": 900}
+
+# The file path where jobs will be stored. A trailing slash is not needed. Note that the default value of
+# this setting is inside the invoking user's home directory.
+# JOBS_ROOT = os.path.expanduser('~/.nautobot/jobs')
+
+# Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal
+# sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging
+# on a production system.
+DEBUG = is_truthy(os.environ.get("DEBUG", False))
+
+# Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce unique IP space
+# within the global table (all prefixes and IP addresses not assigned to a VRF), set
+# ENFORCE_GLOBAL_UNIQUE to True.
+ENFORCE_GLOBAL_UNIQUE = is_truthy(os.environ.get("ENFORCE_GLOBAL_UNIQUE", False))
+
+# Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and
+# by anonymous users. List models in the form `<app>.<model>`. Add '*' to this list to exempt all models.
+EXEMPT_VIEW_PERMISSIONS = [
+    # 'dcim.site',
+    # 'dcim.region',
+    # 'ipam.prefix',
+]
+
+# A list of strings designating all applications that are enabled in this Django installation. Each string should be a dotted Python path to an application configuration class (preferred), or a package containing an application.
+# http://nautobot.readthedocs.io/configuration/optional-settings/#extra-applications
+EXTRA_INSTALLED_APPS = []
+
+# HTTP proxies Nautobot should use when sending outbound HTTP requests (e.g. for webhooks).
+# HTTP_PROXIES = {
+#     'http': 'http://10.10.1.10:3128',
+#     'https': 'http://10.10.1.10:1080',
+# }
+
+# IP addresses recognized as internal to the system. The debugging toolbar will be available only to clients accessing
+# Nautobot from an internal IP.
+INTERNAL_IPS = ("127.0.0.1", "::1")
+
+# The file path where jobs will be stored. A trailing slash is not needed. Note that the default value of
+# this setting is derived from the installed location.
+JOBS_ROOT = os.environ.get("JOBS_ROOT", os.path.expanduser("~/.nautobot/jobs"))
+
+LOG_LEVEL = os.environ.get("LOG_LEVEL", "DEBUG" if DEBUG else "INFO")
+
+# Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs:
+#   https://docs.djangoproject.com/en/stable/topics/logging/
+LOGGING = {
+    "version": 1,
+    "disable_existing_loggers": False,
+    "formatters": {
+        "verbose": {
+            "format": "{asctime} {levelname} {message} - {name} - {module} - {pathname}:{lineno}",
+            "datefmt": "%H:%M:%S",
+            "style": "{",
+        },
+    },
+    "handlers": {"console": {"level": "DEBUG", "class": "rq.utils.ColorizingStreamHandler", "formatter": "verbose"}},
+    "root": {"handlers": ["console"], "level": LOG_LEVEL},
+}
+
+# Setting this to True will display a "maintenance mode" banner at the top of every page.
+MAINTENANCE_MODE = is_truthy(os.environ.get("MAINTENANCE_MODE", False))
+
+# An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g.
+# "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request
+# all objects by specifying "?limit=0".
+MAX_PAGE_SIZE = int(os.environ.get("MAX_PAGE_SIZE", 1000))
+
+# The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that
+# the default value of this setting is within the invoking user's home directory
+MEDIA_ROOT = os.environ.get("MEDIA_ROOT", os.path.expanduser("~/.nautobot/media"))
+
+# Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics'
+METRICS_ENABLED = True
+
+# Determine how many objects to display per page within a list. (Default: 50)
+PAGINATE_COUNT = int(os.environ.get("PAGINATE_COUNT", 50))
+
+# Enable installed plugins. Add the name of each plugin to the list.
+PLUGINS = ["nautobot_chatops", "nautobot_capacity_metrics", "nautobot_chatops_extension_ansible"]
+
+# Plugins configuration settings. These settings are used by various plugins that the user may have installed.
+# Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings.
+PLUGINS_CONFIG = {
+    "nautobot_chatops": {
+        "enable_slack": True,
+        "enable_ms_teams": True,
+        "enable_webex_teams": True,
+        "microsoft_app_id": os.getenv("MICROSOFT_APP_ID"),
+        "microsoft_app_password": os.getenv("MICROSOFT_APP_PASSWORD"),
+        "slack_api_token": os.getenv("SLACK_API_TOKEN"),
+        "slack_signing_secret": os.getenv("SLACK_SIGNING_SECRET"),
+        "slack_slash_command_prefix": os.getenv("SLACK_SLASH_COMMAND_PREFIX", "/"),
+        "webex_teams_token": os.getenv("WEBEX_TEAMS_ACCESS_TOKEN"),
+        "webex_teams_signing_secret": os.getenv("WEBEX_TEAMS_SIGNING_SECRET"),
+        "enable_mattermost": True,
+        "mattermost_api_token": os.getenv("MATTERMOST_API_TOKEN"),
+        "mattermost_url": os.getenv("MATTERMOST_URL"),
+    },
+    "nautobot_chatops_extension_ansible": {
+        "tower_uri": os.getenv("NAUTOBOT_TOWER_URI"),
+        "tower_username": os.getenv("NAUTOBOT_TOWER_USERNAME"),
+        "tower_password": os.getenv("NAUTOBOT_TOWER_PASSWORD"),
+        "tower_verify_ssl": is_truthy(os.getenv("NAUTOBOT_TOWER_VERIFY_SSL", "true")),
+    },
+}
+
+
+# When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to
+# prefer IPv4 instead.
+PREFER_IPV4 = is_truthy(os.environ.get("PREFER_IPV4", False))
+
+# Remote authentication support
+REMOTE_AUTH_ENABLED = False
+REMOTE_AUTH_BACKEND = "nautobot.core.authentication.RemoteUserBackend"
+REMOTE_AUTH_HEADER = "HTTP_REMOTE_USER"
+REMOTE_AUTH_AUTO_CREATE_USER = True
+REMOTE_AUTH_DEFAULT_GROUPS = []
+REMOTE_AUTH_DEFAULT_PERMISSIONS = {}
+
+# This determines how often the GitHub API is called to check the latest release of Nautobot. Must be at least 1 hour.
+RELEASE_CHECK_TIMEOUT = 24 * 3600
+
+# Maximum execution time for background tasks, in seconds.
+RQ_DEFAULT_TIMEOUT = 300
+
+# Configure SSO, for more information see docs/configuration/authentication/sso.md
+SOCIAL_AUTH_ENABLED = False
+
+# By default uploaded media is stored on the local filesystem. Using Django-storages is also supported. Provide the
+# class path of the storage driver in STORAGE_BACKEND and any configuration options in STORAGE_CONFIG. For example:
+# STORAGE_BACKEND = 'storages.backends.s3boto3.S3Boto3Storage'
+# STORAGE_CONFIG = {
+#     'AWS_ACCESS_KEY_ID': 'Key ID',
+#     'AWS_SECRET_ACCESS_KEY': 'Secret',
+#     'AWS_STORAGE_BUCKET_NAME': 'nautobot',
+#     'AWS_S3_REGION_NAME': 'eu-west-1',
+# }
+
+# Time zone (default: UTC)
+TIME_ZONE = os.environ.get("TIME_ZONE", "UTC")
+
+# Django Debug Toolbar
+TESTING = len(sys.argv) > 1 and sys.argv[1] == "test"
+DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda _request: DEBUG and not TESTING}
diff --git a/development/nautobot_main/configuration.py b/development/nautobot_main/configuration.py
deleted file mode 100644
index c317f00..0000000
--- a/development/nautobot_main/configuration.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""Nautobot configuration file overrides specific to the latest v1.0.0 version."""
-
-# Make the django-debug-toolbar always visible when DEBUG is enabled,
-# except when we're running Django unit-tests.
-import sys  # pylint: disable=wrong-import-order
-
-TESTING = len(sys.argv) > 1 and sys.argv[1] == "test"
-DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda: DEBUG and not TESTING}  # pylint: disable=undefined-variable
-
-# Overrides specific to this version go here
diff --git a/development/nautobot_v1.0.0b3/configuration.py b/development/nautobot_v1.0.0b3/configuration.py
deleted file mode 100644
index c317f00..0000000
--- a/development/nautobot_v1.0.0b3/configuration.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""Nautobot configuration file overrides specific to the latest v1.0.0 version."""
-
-# Make the django-debug-toolbar always visible when DEBUG is enabled,
-# except when we're running Django unit-tests.
-import sys  # pylint: disable=wrong-import-order
-
-TESTING = len(sys.argv) > 1 and sys.argv[1] == "test"
-DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda: DEBUG and not TESTING}  # pylint: disable=undefined-variable
-
-# Overrides specific to this version go here
diff --git a/nautobot_plugin_chatops_ansible/__init__.py b/nautobot_chatops_extension_ansible/__init__.py
similarity index 72%
rename from nautobot_plugin_chatops_ansible/__init__.py
rename to nautobot_chatops_extension_ansible/__init__.py
index 8b0d36e..c45f531 100644
--- a/nautobot_plugin_chatops_ansible/__init__.py
+++ b/nautobot_chatops_extension_ansible/__init__.py
@@ -1,4 +1,4 @@
-"""Plugin declaration for nautobot_plugin_chatops_ansible."""
+"""Plugin declaration for nautobot_chatops_extension_ansible."""
 
 __version__ = "0.0.1"
 
@@ -6,9 +6,9 @@
 
 
 class NautobotAnsibleConfig(PluginConfig):
-    """Plugin configuration for the nautobot_plugin_chatops_ansible plugin."""
+    """Plugin configuration for the nautobot_chatops_extension_ansible plugin."""
 
-    name = "nautobot_plugin_chatops_ansible"
+    name = "nautobot_chatops_extension_ansible"
     verbose_name = "Nautobot Nautobot Ansible Tower integration"
     version = __version__
     author = "Network to Code, LLC"
diff --git a/nautobot_plugin_chatops_ansible/static/nautobot_ansible/Ansible_Logo.png b/nautobot_chatops_extension_ansible/static/nautobot_ansible/Ansible_Logo.png
similarity index 100%
rename from nautobot_plugin_chatops_ansible/static/nautobot_ansible/Ansible_Logo.png
rename to nautobot_chatops_extension_ansible/static/nautobot_ansible/Ansible_Logo.png
diff --git a/nautobot_plugin_chatops_ansible/tower.py b/nautobot_chatops_extension_ansible/tower.py
similarity index 94%
rename from nautobot_plugin_chatops_ansible/tower.py
rename to nautobot_chatops_extension_ansible/tower.py
index 2742cbe..6974402 100644
--- a/nautobot_plugin_chatops_ansible/tower.py
+++ b/nautobot_chatops_extension_ansible/tower.py
@@ -5,6 +5,8 @@
 
 import requests
 
+from nautobot.core.settings_funcs import is_truthy
+
 logger = logging.getLogger("rq.worker")
 
 
@@ -17,8 +19,8 @@ def __init__(
         tower_uri=os.getenv("NAUTOBOT_TOWER_URI"),
         username=os.getenv("NAUTOBOT_TOWER_USERNAME"),
         password=os.getenv("NAUTOBOT_TOWER_PASSWORD"),
-        verify_ssl=os.getenv("NAUTOBOT_TOWER_VERIFY_SSL", True),
-    ):
+        verify_ssl=is_truthy(os.getenv("NAUTOBOT_TOWER_VERIFY_SSL", "true")),
+    ):  # pylint: disable=too-many-arguments
         """Initialization of Tower class.
 
         Args:
@@ -84,7 +86,7 @@ def get_tower_inventories(self):
         """Gets inventory of devices in Ansible Tower."""
         return self._get_tower("inventories/")
 
-    def get_tower_inventory_id(self, inventory_name: str):
+    def get_tower_inventory_id(self, inventory_name: str):  # pylint: disable=inconsistent-return-statements
         """Gets Tower inventory ID from list of inventories.
 
         Args:
@@ -109,7 +111,7 @@ def get_tower_inventory_groups(self, inventory):
         """
         return self._get_tower(f"inventories/{inventory}/groups/")
 
-    def get_tower_group_id(self, inventory_id: int, group_name: str):
+    def get_tower_group_id(self, inventory_id: int, group_name: str):  # pylint: disable=inconsistent-return-statements
         """Gets Group ID from groups.
 
         Args:
diff --git a/nautobot_plugin_chatops_ansible/worker.py b/nautobot_chatops_extension_ansible/worker.py
similarity index 100%
rename from nautobot_plugin_chatops_ansible/worker.py
rename to nautobot_chatops_extension_ansible/worker.py
diff --git a/poetry.lock b/poetry.lock
index bbe65f6..88453c4 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,23 +1,3 @@
-[[package]]
-name = "aiohttp"
-version = "3.7.4.post0"
-description = "Async http client/server framework (asyncio)"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-async-timeout = ">=3.0,<4.0"
-attrs = ">=17.3.0"
-chardet = ">=2.0,<5.0"
-idna-ssl = {version = ">=1.0", markers = "python_version < \"3.7\""}
-multidict = ">=4.5,<7.0"
-typing-extensions = ">=3.6.5"
-yarl = ">=1.0,<2.0"
-
-[package.extras]
-speedups = ["aiodns", "brotlipy", "cchardet"]
-
 [[package]]
 name = "aniso8601"
 version = "7.0.0"
@@ -50,38 +30,30 @@ tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
 
 [[package]]
 name = "astroid"
-version = "2.5.3"
+version = "2.5.6"
 description = "An abstract syntax tree for Python with inference support."
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = "~=3.6"
 
 [package.dependencies]
 lazy-object-proxy = ">=1.4.0"
 typed-ast = {version = ">=1.4.0,<1.5", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""}
 wrapt = ">=1.11,<1.13"
 
-[[package]]
-name = "async-timeout"
-version = "3.0.1"
-description = "Timeout context manager for asyncio programs"
-category = "main"
-optional = false
-python-versions = ">=3.5.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"
@@ -150,17 +122,21 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
 name = "click"
-version = "7.1.2"
+version = "8.0.1"
 description = "Composable command line interface toolkit"
 category = "main"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = ">=3.6"
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
 
 [[package]]
 name = "colorama"
 version = "0.4.4"
 description = "Cross-platform colored terminal text."
-category = "dev"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
@@ -189,6 +165,17 @@ python-versions = "*"
 [package.dependencies]
 jinja2 = "*"
 
+[[package]]
+name = "coverage"
+version = "5.4"
+description = "Code coverage measurement for Python"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+
+[package.extras]
+toml = ["toml"]
+
 [[package]]
 name = "cryptography"
 version = "3.4.7"
@@ -226,7 +213,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
 name = "django"
-version = "3.1.8"
+version = "3.1.12"
 description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
 category = "main"
 optional = false
@@ -321,26 +308,27 @@ Django = ">=1.11"
 django-js-asset = "*"
 
 [[package]]
-name = "django-pglocks"
-version = "1.0.4"
-description = "django_pglocks provides useful context managers for advisory locks for PostgreSQL."
+name = "django-prometheus"
+version = "2.1.0"
+description = "Django middlewares to monitor your application with Prometheus.io."
 category = "main"
 optional = false
 python-versions = "*"
 
 [package.dependencies]
-six = ">=1.0.0"
+prometheus-client = ">=0.7"
 
 [[package]]
-name = "django-prometheus"
-version = "2.1.0"
-description = "Django middlewares to monitor your application with Prometheus.io."
+name = "django-redis"
+version = "4.12.1"
+description = "Full featured redis cache backend for Django."
 category = "main"
 optional = false
-python-versions = "*"
+python-versions = ">=3.5"
 
 [package.dependencies]
-prometheus-client = ">=0.7"
+Django = ">=2.2"
+redis = ">=3.0.0"
 
 [[package]]
 name = "django-rq"
@@ -452,7 +440,7 @@ validation = ["swagger-spec-validator (>=2.1.0)"]
 
 [[package]]
 name = "funcy"
-version = "1.15"
+version = "1.16"
 description = "A fancy and practical functional tools"
 category = "main"
 optional = false
@@ -479,14 +467,15 @@ smmap = ">=3.0.1,<5"
 
 [[package]]
 name = "gitpython"
-version = "3.1.14"
+version = "3.1.17"
 description = "Python Git Library"
 category = "main"
 optional = false
-python-versions = ">=3.4"
+python-versions = ">=3.5"
 
 [package.dependencies]
 gitdb = ">=4.0.1,<5"
+typing-extensions = {version = ">=3.7.4.0", markers = "python_version < \"3.8\""}
 
 [[package]]
 name = "graphene"
@@ -567,17 +556,6 @@ category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
-[[package]]
-name = "idna-ssl"
-version = "1.1.0"
-description = "Patch ssl.match_hostname for Unicode(idna) domains support"
-category = "main"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-idna = ">=2.0"
-
 [[package]]
 name = "importlib-metadata"
 version = "3.4.0"
@@ -596,18 +574,18 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake
 
 [[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 = "inflection"
@@ -621,7 +599,7 @@ python-versions = ">=3.5"
 name = "invoke"
 version = "1.5.0"
 description = "Pythonic task execution"
-category = "main"
+category = "dev"
 optional = false
 python-versions = "*"
 
@@ -702,11 +680,11 @@ testing = ["coverage", "pyyaml"]
 
 [[package]]
 name = "markupsafe"
-version = "1.1.1"
+version = "2.0.1"
 description = "Safely add untrusted strings to HTML/XML markup."
 category = "main"
 optional = false
-python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
+python-versions = ">=3.6"
 
 [[package]]
 name = "mccabe"
@@ -716,14 +694,6 @@ category = "dev"
 optional = false
 python-versions = "*"
 
-[[package]]
-name = "multidict"
-version = "5.1.0"
-description = "multidict implementation"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
 [[package]]
 name = "mypy-extensions"
 version = "0.4.3"
@@ -734,45 +704,45 @@ python-versions = "*"
 
 [[package]]
 name = "nautobot"
-version = "1.0.0b3"
+version = "1.0.2"
 description = "Source of truth and network automation platform."
 category = "main"
 optional = false
 python-versions = ">=3.6,<4.0"
 
 [package.dependencies]
-Django = ">=3.1.7,<3.2.0"
+Django = ">=3.1.8,<3.2.0"
 django-cacheops = ">=5.1,<5.2"
 django-cors-headers = ">=3.7.0,<3.8.0"
 django-cryptography = ">=1.0,<1.1"
 django-filter = ">=2.4.0,<2.5.0"
 django-mptt = ">=0.11.0,<0.12.0"
-django-pglocks = ">=1.0.4,<1.1.0"
 django-prometheus = ">=2.1.0,<2.2.0"
-django-rq = ">=2.4.0,<3.0.0"
+django-redis = ">=4.12.1,<4.13.0"
+django-rq = ">=2.4.1,<2.5.0"
 django-tables2 = ">=2.3.4,<2.4.0"
 django-taggit = ">=1.3.0,<1.4.0"
-django-timezone-field = ">=4.1.1,<4.2.0"
+django-timezone-field = ">=4.1.2,<4.2.0"
 django-webserver = ">=1.2.0,<1.3.0"
-djangorestframework = ">=3.12.2,<3.13.0"
+djangorestframework = ">=3.12.4,<3.13.0"
 drf-yasg = {version = ">=1.20.0,<1.21.0", extras = ["validation"]}
-GitPython = ">=3.1.13,<3.2.0"
+GitPython = ">=3.1.15,<3.2.0"
 graphene-django = ">=2.15.0,<2.16.0"
 importlib-metadata = {version = ">=3.4.0,<3.5.0", markers = "python_version < \"3.8\""}
 Jinja2 = ">=2.11.3,<2.12.0"
-Markdown = ">=3.3.3,<3.4.0"
+Markdown = ">=3.3.4,<3.4.0"
 netaddr = ">=0.8.0,<0.9.0"
 Pillow = ">=8.1.0,<8.2.0"
 psycopg2-binary = ">=2.8.6,<2.9.0"
 pycryptodome = ">=3.10.1,<3.11.0"
-pyuwsgi = ">=2.0.19,<3.0.0"
+pyuwsgi = ">=2.0.19.1.post0,<2.1.0.0"
 PyYAML = ">=5.4.1,<5.5.0"
 social-auth-app-django = ">=4.0.0,<5.0.0"
 svgwrite = ">=1.4.1,<1.5.0"
 
 [[package]]
 name = "nautobot-capacity-metrics"
-version = "1.0.0"
+version = "1.0.1"
 description = "Plugin to improve the instrumentation of Nautobot and expose additional metrics (Application Metrics, RQ Worker)."
 category = "main"
 optional = false
@@ -783,7 +753,7 @@ nautobot = "*"
 
 [[package]]
 name = "nautobot-chatops"
-version = "1.1.0"
+version = "1.3.1"
 description = "A plugin providing chatbot capabilities for Nautobot"
 category = "main"
 optional = false
@@ -792,8 +762,8 @@ python-versions = ">=3.6,<4.0"
 [package.dependencies]
 nautobot = "*"
 nautobot-capacity-metrics = "*"
-pyjwt = {version = ">=1.7.1,<2.0.0", extras = ["crypto"]}
-slackclient = ">=2.7.1,<3.0.0"
+PyJWT = ">=2.1.0,<3.0.0"
+slack-sdk = ">=3.4.2,<4.0.0"
 texttable = ">=1.6.2,<2.0.0"
 webexteamssdk = ">=1.3,<2.0"
 
@@ -810,16 +780,16 @@ importlib-resources = {version = "*", markers = "python_version < \"3.7\""}
 
 [[package]]
 name = "oauthlib"
-version = "3.1.0"
+version = "3.1.1"
 description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
 category = "main"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.6"
 
 [package.extras]
-rsa = ["cryptography"]
-signals = ["blinker"]
-signedtoken = ["cryptography", "pyjwt (>=1.0.0)"]
+rsa = ["cryptography (>=3.0.0,<4)"]
+signals = ["blinker (>=1.4.0)"]
+signedtoken = ["cryptography (>=3.0.0,<4)", "pyjwt (>=2.0.0,<3)"]
 
 [[package]]
 name = "packaging"
@@ -842,7 +812,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
 name = "pbr"
-version = "5.5.1"
+version = "5.6.0"
 description = "Python Build Reasonableness"
 category = "dev"
 optional = false
@@ -858,7 +828,7 @@ python-versions = ">=3.6"
 
 [[package]]
 name = "prometheus-client"
-version = "0.10.1"
+version = "0.11.0"
 description = "Python client for the Prometheus monitoring system."
 category = "main"
 optional = false
@@ -918,41 +888,36 @@ snowballstemmer = "*"
 
 [[package]]
 name = "pyjwt"
-version = "1.7.1"
+version = "2.1.0"
 description = "JSON Web Token implementation in Python"
 category = "main"
 optional = false
-python-versions = "*"
-
-[package.dependencies]
-cryptography = {version = ">=1.4", optional = true, markers = "extra == \"crypto\""}
+python-versions = ">=3.6"
 
 [package.extras]
-crypto = ["cryptography (>=1.4)"]
-flake8 = ["flake8", "flake8-import-order", "pep8-naming"]
-test = ["pytest (>=4.0.1,<5.0.0)", "pytest-cov (>=2.6.0,<3.0.0)", "pytest-runner (>=4.2,<5.0.0)"]
+crypto = ["cryptography (>=3.3.1,<4.0.0)"]
+dev = ["sphinx", "sphinx-rtd-theme", "zope.interface", "cryptography (>=3.3.1,<4.0.0)", "pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)", "mypy", "pre-commit"]
+docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
+tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"]
 
 [[package]]
 name = "pylint"
-version = "2.7.4"
+version = "2.8.3"
 description = "python code static checker"
 category = "dev"
 optional = false
 python-versions = "~=3.6"
 
 [package.dependencies]
-astroid = ">=2.5.2,<2.7"
+astroid = "2.5.6"
 colorama = {version = "*", markers = "sys_platform == \"win32\""}
 isort = ">=4.2.5,<6"
 mccabe = ">=0.6,<0.7"
 toml = ">=0.7.1"
 
-[package.extras]
-docs = ["sphinx (==3.5.1)", "python-docs-theme (==2020.12)"]
-
 [[package]]
 name = "pylint-django"
-version = "2.4.3"
+version = "2.4.4"
 description = "A Pylint plugin to help Pylint understand the Django web framework"
 category = "dev"
 optional = false
@@ -1113,7 +1078,7 @@ requests = ">=2.0.1,<3.0.0"
 
 [[package]]
 name = "rq"
-version = "1.8.0"
+version = "1.8.1"
 description = "RQ is a simple, lightweight, library for creating background jobs, and processing them."
 category = "main"
 optional = false
@@ -1125,7 +1090,7 @@ redis = ">=3.5.0"
 
 [[package]]
 name = "ruamel.yaml"
-version = "0.17.4"
+version = "0.17.9"
 description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order"
 category = "main"
 optional = false
@@ -1156,7 +1121,7 @@ python-versions = "*"
 
 [[package]]
 name = "singledispatch"
-version = "3.6.1"
+version = "3.6.2"
 description = "Backport functools.singledispatch from Python 3.4 to Python 2.6-3.3."
 category = "main"
 optional = false
@@ -1167,29 +1132,27 @@ six = "*"
 
 [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-black (>=0.3.7)", "unittest2"]
+testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "unittest2", "pytest-checkdocs (>=2.4)"]
 
 [[package]]
 name = "six"
-version = "1.15.0"
+version = "1.16.0"
 description = "Python 2 and 3 compatibility utilities"
 category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
 
 [[package]]
-name = "slackclient"
-version = "2.9.3"
-description = "Slack API clients for Web API and RTM API"
+name = "slack-sdk"
+version = "3.6.0"
+description = "The Slack API Platform SDK for Python"
 category = "main"
 optional = false
 python-versions = ">=3.6.0"
 
-[package.dependencies]
-aiohttp = ">3.5.2,<4.0.0"
-
 [package.extras]
-optional = ["aiodns (>1.0)"]
+optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=9.1,<10)", "websocket-client (>=0.57,<1)"]
+testing = ["pytest (>=5.4,<6)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=3,<4)", "black (==21.5b1)", "psutil (>=5,<6)", "databases (>=0.3)"]
 
 [[package]]
 name = "smmap"
@@ -1221,27 +1184,26 @@ social-auth-core = ">=3.3.0"
 
 [[package]]
 name = "social-auth-core"
-version = "4.0.2"
+version = "4.1.0"
 description = "Python social authentication made simple."
 category = "main"
 optional = false
-python-versions = "*"
+python-versions = ">=3.6"
 
 [package.dependencies]
 cryptography = ">=1.4"
-defusedxml = {version = ">=0.5.0rc1", markers = "python_version >= \"3.0\""}
+defusedxml = ">=0.5.0rc1"
 oauthlib = ">=1.0.3"
-PyJWT = ">=1.4.0,<2"
-python3-openid = {version = ">=3.0.10", markers = "python_version >= \"3.0\""}
+PyJWT = ">=2.0.0"
+python3-openid = ">=3.0.10"
 requests = ">=2.9.1"
 requests-oauthlib = ">=0.6.1"
-six = ">=1.10.0"
 
 [package.extras]
-all = ["python-jose (>=3.0.0)", "pyjwt (>=1.7.1)", "python3-saml (>=1.2.1)", "cryptography (>=2.1.1)"]
-allpy3 = ["python-jose (>=3.0.0)", "pyjwt (>=1.7.1)", "python3-saml (>=1.2.1)", "cryptography (>=2.1.1)", "defusedxml (>=0.5.0rc1)", "python3-openid (>=3.0.10)"]
+all = ["python-jose (>=3.0.0)", "python3-saml (>=1.2.1)", "cryptography (>=2.1.1)"]
+allpy3 = ["python-jose (>=3.0.0)", "python3-saml (>=1.2.1)", "cryptography (>=2.1.1)"]
 azuread = ["cryptography (>=2.1.1)"]
-openidconnect = ["python-jose (>=3.0.0)", "pyjwt (>=1.7.1)"]
+openidconnect = ["python-jose (>=3.0.0)"]
 saml = ["python3-saml (>=1.2.1)"]
 
 [[package]]
@@ -1319,7 +1281,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
@@ -1380,19 +1342,6 @@ python-versions = ">=3.5.*"
 pathspec = ">=0.5.3"
 pyyaml = "*"
 
-[[package]]
-name = "yarl"
-version = "1.6.3"
-description = "Yet another URL library"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-idna = ">=2.0"
-multidict = ">=4.0"
-typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
-
 [[package]]
 name = "zipp"
 version = "3.4.1"
@@ -1408,48 +1357,9 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.6"
-content-hash = "ab325f85479f194644a55153277a078866fbd8d05b38ad3aae717492ae6fb49f"
+content-hash = "44980d2fa8f29b688449b8fa93780d6bbd8294bf83453db6bf960c5a4e5a2da6"
 
 [metadata.files]
-aiohttp = [
-    {file = "aiohttp-3.7.4.post0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5"},
-    {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8"},
-    {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"},
-    {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290"},
-    {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f"},
-    {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809"},
-    {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe"},
-    {file = "aiohttp-3.7.4.post0-cp36-cp36m-win32.whl", hash = "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287"},
-    {file = "aiohttp-3.7.4.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc"},
-    {file = "aiohttp-3.7.4.post0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87"},
-    {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0"},
-    {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970"},
-    {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f"},
-    {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde"},
-    {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c"},
-    {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8"},
-    {file = "aiohttp-3.7.4.post0-cp37-cp37m-win32.whl", hash = "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f"},
-    {file = "aiohttp-3.7.4.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5"},
-    {file = "aiohttp-3.7.4.post0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf"},
-    {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df"},
-    {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213"},
-    {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4"},
-    {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009"},
-    {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5"},
-    {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013"},
-    {file = "aiohttp-3.7.4.post0-cp38-cp38-win32.whl", hash = "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16"},
-    {file = "aiohttp-3.7.4.post0-cp38-cp38-win_amd64.whl", hash = "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5"},
-    {file = "aiohttp-3.7.4.post0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b"},
-    {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd"},
-    {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439"},
-    {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22"},
-    {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a"},
-    {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb"},
-    {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb"},
-    {file = "aiohttp-3.7.4.post0-cp39-cp39-win32.whl", hash = "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9"},
-    {file = "aiohttp-3.7.4.post0-cp39-cp39-win_amd64.whl", hash = "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe"},
-    {file = "aiohttp-3.7.4.post0.tar.gz", hash = "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf"},
-]
 aniso8601 = [
     {file = "aniso8601-7.0.0-py2.py3-none-any.whl", hash = "sha256:d10a4bf949f619f719b227ef5386e31f49a2b6d453004b21f02661ccc8670c7b"},
     {file = "aniso8601-7.0.0.tar.gz", hash = "sha256:513d2b6637b7853806ae79ffaca6f3e8754bdd547048f5ccc1420aec4b714f1e"},
@@ -1463,16 +1373,12 @@ asgiref = [
     {file = "asgiref-3.3.4.tar.gz", hash = "sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78"},
 ]
 astroid = [
-    {file = "astroid-2.5.3-py3-none-any.whl", hash = "sha256:bea3f32799fbb8581f58431c12591bc20ce11cbc90ad82e2ea5717d94f2080d5"},
-    {file = "astroid-2.5.3.tar.gz", hash = "sha256:ad63b8552c70939568966811a088ef0bc880f99a24a00834abd0e3681b514f91"},
-]
-async-timeout = [
-    {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"},
-    {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"},
+    {file = "astroid-2.5.6-py3-none-any.whl", hash = "sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e"},
+    {file = "astroid-2.5.6.tar.gz", hash = "sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975"},
 ]
 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"},
@@ -1529,8 +1435,8 @@ chardet = [
     {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
 ]
 click = [
-    {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
-    {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
+    {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
+    {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
 ]
 colorama = [
     {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
@@ -1544,6 +1450,57 @@ coreschema = [
     {file = "coreschema-0.0.4-py2-none-any.whl", hash = "sha256:5e6ef7bf38c1525d5e55a895934ab4273548629f16aed5c0a6caa74ebf45551f"},
     {file = "coreschema-0.0.4.tar.gz", hash = "sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607"},
 ]
+coverage = [
+    {file = "coverage-5.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:6d9c88b787638a451f41f97446a1c9fd416e669b4d9717ae4615bd29de1ac135"},
+    {file = "coverage-5.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:66a5aae8233d766a877c5ef293ec5ab9520929c2578fd2069308a98b7374ea8c"},
+    {file = "coverage-5.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9754a5c265f991317de2bac0c70a746efc2b695cf4d49f5d2cddeac36544fb44"},
+    {file = "coverage-5.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:fbb17c0d0822684b7d6c09915677a32319f16ff1115df5ec05bdcaaee40b35f3"},
+    {file = "coverage-5.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:b7f7421841f8db443855d2854e25914a79a1ff48ae92f70d0a5c2f8907ab98c9"},
+    {file = "coverage-5.4-cp27-cp27m-win32.whl", hash = "sha256:4a780807e80479f281d47ee4af2eb2df3e4ccf4723484f77da0bb49d027e40a1"},
+    {file = "coverage-5.4-cp27-cp27m-win_amd64.whl", hash = "sha256:87c4b38288f71acd2106f5d94f575bc2136ea2887fdb5dfe18003c881fa6b370"},
+    {file = "coverage-5.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6809ebcbf6c1049002b9ac09c127ae43929042ec1f1dbd8bb1615f7cd9f70a0"},
+    {file = "coverage-5.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ba7ca81b6d60a9f7a0b4b4e175dcc38e8fef4992673d9d6e6879fd6de00dd9b8"},
+    {file = "coverage-5.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:89fc12c6371bf963809abc46cced4a01ca4f99cba17be5e7d416ed7ef1245d19"},
+    {file = "coverage-5.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a8eb7785bd23565b542b01fb39115a975fefb4a82f23d407503eee2c0106247"},
+    {file = "coverage-5.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:7e40d3f8eb472c1509b12ac2a7e24158ec352fc8567b77ab02c0db053927e339"},
+    {file = "coverage-5.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1ccae21a076d3d5f471700f6d30eb486da1626c380b23c70ae32ab823e453337"},
+    {file = "coverage-5.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:755c56beeacac6a24c8e1074f89f34f4373abce8b662470d3aa719ae304931f3"},
+    {file = "coverage-5.4-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:322549b880b2d746a7672bf6ff9ed3f895e9c9f108b714e7360292aa5c5d7cf4"},
+    {file = "coverage-5.4-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:60a3307a84ec60578accd35d7f0c71a3a971430ed7eca6567399d2b50ef37b8c"},
+    {file = "coverage-5.4-cp35-cp35m-win32.whl", hash = "sha256:1375bb8b88cb050a2d4e0da901001347a44302aeadb8ceb4b6e5aa373b8ea68f"},
+    {file = "coverage-5.4-cp35-cp35m-win_amd64.whl", hash = "sha256:16baa799ec09cc0dcb43a10680573269d407c159325972dd7114ee7649e56c66"},
+    {file = "coverage-5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2f2cf7a42d4b7654c9a67b9d091ec24374f7c58794858bff632a2039cb15984d"},
+    {file = "coverage-5.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b62046592b44263fa7570f1117d372ae3f310222af1fc1407416f037fb3af21b"},
+    {file = "coverage-5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:812eaf4939ef2284d29653bcfee9665f11f013724f07258928f849a2306ea9f9"},
+    {file = "coverage-5.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:859f0add98707b182b4867359e12bde806b82483fb12a9ae868a77880fc3b7af"},
+    {file = "coverage-5.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:04b14e45d6a8e159c9767ae57ecb34563ad93440fc1b26516a89ceb5b33c1ad5"},
+    {file = "coverage-5.4-cp36-cp36m-win32.whl", hash = "sha256:ebfa374067af240d079ef97b8064478f3bf71038b78b017eb6ec93ede1b6bcec"},
+    {file = "coverage-5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:84df004223fd0550d0ea7a37882e5c889f3c6d45535c639ce9802293b39cd5c9"},
+    {file = "coverage-5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1b811662ecf72eb2d08872731636aee6559cae21862c36f74703be727b45df90"},
+    {file = "coverage-5.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6b588b5cf51dc0fd1c9e19f622457cc74b7d26fe295432e434525f1c0fae02bc"},
+    {file = "coverage-5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3fe50f1cac369b02d34ad904dfe0771acc483f82a1b54c5e93632916ba847b37"},
+    {file = "coverage-5.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:32ab83016c24c5cf3db2943286b85b0a172dae08c58d0f53875235219b676409"},
+    {file = "coverage-5.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:68fb816a5dd901c6aff352ce49e2a0ffadacdf9b6fae282a69e7a16a02dad5fb"},
+    {file = "coverage-5.4-cp37-cp37m-win32.whl", hash = "sha256:a636160680c6e526b84f85d304e2f0bb4e94f8284dd765a1911de9a40450b10a"},
+    {file = "coverage-5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:bb32ca14b4d04e172c541c69eec5f385f9a075b38fb22d765d8b0ce3af3a0c22"},
+    {file = "coverage-5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4d7165a4e8f41eca6b990c12ee7f44fef3932fac48ca32cecb3a1b2223c21f"},
+    {file = "coverage-5.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a565f48c4aae72d1d3d3f8e8fb7218f5609c964e9c6f68604608e5958b9c60c3"},
+    {file = "coverage-5.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fff1f3a586246110f34dc762098b5afd2de88de507559e63553d7da643053786"},
+    {file = "coverage-5.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:a839e25f07e428a87d17d857d9935dd743130e77ff46524abb992b962eb2076c"},
+    {file = "coverage-5.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:6625e52b6f346a283c3d563d1fd8bae8956daafc64bb5bbd2b8f8a07608e3994"},
+    {file = "coverage-5.4-cp38-cp38-win32.whl", hash = "sha256:5bee3970617b3d74759b2d2df2f6a327d372f9732f9ccbf03fa591b5f7581e39"},
+    {file = "coverage-5.4-cp38-cp38-win_amd64.whl", hash = "sha256:03ed2a641e412e42cc35c244508cf186015c217f0e4d496bf6d7078ebe837ae7"},
+    {file = "coverage-5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14a9f1887591684fb59fdba8feef7123a0da2424b0652e1b58dd5b9a7bb1188c"},
+    {file = "coverage-5.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9564ac7eb1652c3701ac691ca72934dd3009997c81266807aef924012df2f4b3"},
+    {file = "coverage-5.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0f48fc7dc82ee14aeaedb986e175a429d24129b7eada1b7e94a864e4f0644dde"},
+    {file = "coverage-5.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:107d327071061fd4f4a2587d14c389a27e4e5c93c7cba5f1f59987181903902f"},
+    {file = "coverage-5.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:0cdde51bfcf6b6bd862ee9be324521ec619b20590787d1655d005c3fb175005f"},
+    {file = "coverage-5.4-cp39-cp39-win32.whl", hash = "sha256:c67734cff78383a1f23ceba3b3239c7deefc62ac2b05fa6a47bcd565771e5880"},
+    {file = "coverage-5.4-cp39-cp39-win_amd64.whl", hash = "sha256:c669b440ce46ae3abe9b2d44a913b5fd86bb19eb14a8701e88e3918902ecd345"},
+    {file = "coverage-5.4-pp36-none-any.whl", hash = "sha256:c0ff1c1b4d13e2240821ef23c1efb1f009207cb3f56e16986f713c2b0e7cd37f"},
+    {file = "coverage-5.4-pp37-none-any.whl", hash = "sha256:cd601187476c6bed26a0398353212684c427e10a903aeafa6da40c63309d438b"},
+    {file = "coverage-5.4.tar.gz", hash = "sha256:6d2e262e5e8da6fa56e774fb8e2643417351427604c2b177f8e8c5f75fc928ca"},
+]
 cryptography = [
     {file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"},
     {file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"},
@@ -1567,8 +1524,8 @@ defusedxml = [
     {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"},
 ]
 django = [
-    {file = "Django-3.1.8-py3-none-any.whl", hash = "sha256:c348b3ddc452bf4b62361f0752f71a339140c777ebea3cdaaaa8fdb7f417a862"},
-    {file = "Django-3.1.8.tar.gz", hash = "sha256:f8393103e15ec2d2d313ccbb95a3f1da092f9f58d74ac1c61ca2ac0436ae1eac"},
+    {file = "Django-3.1.12-py3-none-any.whl", hash = "sha256:a523d62b7ab2908f551dabc32b99017a86aa7784e32b761708e52be3dce6d35d"},
+    {file = "Django-3.1.12.tar.gz", hash = "sha256:dc41bf07357f1f4810c1c555b685cb51f780b41e37892d6cc92b89789f2847e1"},
 ]
 django-appconf = [
     {file = "django-appconf-1.0.4.tar.gz", hash = "sha256:be58deb54a43d77d2e1621fe59f787681376d3cd0b8bd8e4758ef6c3a6453380"},
@@ -1598,13 +1555,14 @@ django-mptt = [
     {file = "django-mptt-0.11.0.tar.gz", hash = "sha256:dfdb3af75ad27cdd4458b0544ec8574174f2b90f99bc2cafab6a15b4bc1895a8"},
     {file = "django_mptt-0.11.0-py2.py3-none-any.whl", hash = "sha256:90eb236eb4f1a92124bd7c37852bbe09c0d21158477cc237556d59842a91c509"},
 ]
-django-pglocks = [
-    {file = "django-pglocks-1.0.4.tar.gz", hash = "sha256:3c47c66fbfbd268ef46269673a0516a039539b0972b8ed2ec9cfee44c4b65523"},
-]
 django-prometheus = [
     {file = "django-prometheus-2.1.0.tar.gz", hash = "sha256:dd3f8da1399140fbef5c00d1526a23d1ade286b144281c325f8e409a781643f2"},
     {file = "django_prometheus-2.1.0-py2.py3-none-any.whl", hash = "sha256:c338d6efde1ca336e90c540b5e87afe9287d7bcc82d651a778f302b0be17a933"},
 ]
+django-redis = [
+    {file = "django-redis-4.12.1.tar.gz", hash = "sha256:306589c7021e6468b2656edc89f62b8ba67e8d5a1c8877e2688042263daa7a63"},
+    {file = "django_redis-4.12.1-py3-none-any.whl", hash = "sha256:1133b26b75baa3664164c3f44b9d5d133d1b8de45d94d79f38d1adc5b1d502e5"},
+]
 django-rq = [
     {file = "django-rq-2.4.1.tar.gz", hash = "sha256:f09059ab37403a47c7933bca396fabb7f3058732d132462eade5333bc4bcac5f"},
     {file = "django_rq-2.4.1-py2.py3-none-any.whl", hash = "sha256:23981f83c537178cbbf730f192c13e99cede2204e9d917b1c1c80c42215dd227"},
@@ -1634,8 +1592,8 @@ drf-yasg = [
     {file = "drf_yasg-1.20.0-py2.py3-none-any.whl", hash = "sha256:8b72e5b1875931a8d11af407be3a9a5ba8776541492947a0df5bafda6b7f8267"},
 ]
 funcy = [
-    {file = "funcy-1.15-py2.py3-none-any.whl", hash = "sha256:c247c3d085e03a89974e0c9e8331e9a79db3768a263556ba896d6c92d665bba2"},
-    {file = "funcy-1.15.tar.gz", hash = "sha256:65b746fed572b392d886810a98d56939c6e0d545abb750527a717c21ced21008"},
+    {file = "funcy-1.16-py2.py3-none-any.whl", hash = "sha256:1d3fc5d42cf7564a6b2be04042d0df7a50c77903cf760a34786d0c9ebd659b25"},
+    {file = "funcy-1.16.tar.gz", hash = "sha256:2775409b7dc9106283f1224d97e6df5f2c02e7291c8caed72764f5a115dffb50"},
 ]
 future = [
     {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
@@ -1645,8 +1603,8 @@ gitdb = [
     {file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"},
 ]
 gitpython = [
-    {file = "GitPython-3.1.14-py3-none-any.whl", hash = "sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b"},
-    {file = "GitPython-3.1.14.tar.gz", hash = "sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61"},
+    {file = "GitPython-3.1.17-py3-none-any.whl", hash = "sha256:29fe82050709760081f588dd50ce83504feddbebdc4da6956d02351552b1c135"},
+    {file = "GitPython-3.1.17.tar.gz", hash = "sha256:ee24bdc93dce357630764db659edaf6b8d664d4ff5447ccfeedd2dc5c253f41e"},
 ]
 graphene = [
     {file = "graphene-2.1.8-py2.py3-none-any.whl", hash = "sha256:09165f03e1591b76bf57b133482db9be6dac72c74b0a628d3c93182af9c5a896"},
@@ -1668,16 +1626,13 @@ idna = [
     {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
     {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
 ]
-idna-ssl = [
-    {file = "idna-ssl-1.1.0.tar.gz", hash = "sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c"},
-]
 importlib-metadata = [
     {file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"},
     {file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"},
 ]
 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"},
 ]
 inflection = [
     {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"},
@@ -1733,106 +1688,68 @@ markdown = [
     {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"},
 ]
 markupsafe = [
-    {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
-    {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
-    {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"},
-    {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"},
-    {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"},
-    {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"},
-    {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"},
-    {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"},
-    {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"},
-    {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"},
-    {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"},
-    {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"},
-    {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"},
-    {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"},
-    {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"},
-    {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
-    {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
-    {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
-    {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
-    {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
-    {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
-    {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
-    {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
-    {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
-    {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
-    {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
-    {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
-    {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
-    {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
-    {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
-    {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
-    {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
-    {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
+    {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
 ]
 mccabe = [
     {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
     {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
 ]
-multidict = [
-    {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"},
-    {file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"},
-    {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281"},
-    {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d"},
-    {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d"},
-    {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da"},
-    {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224"},
-    {file = "multidict-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26"},
-    {file = "multidict-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6"},
-    {file = "multidict-5.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76"},
-    {file = "multidict-5.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a"},
-    {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f"},
-    {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348"},
-    {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93"},
-    {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9"},
-    {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37"},
-    {file = "multidict-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5"},
-    {file = "multidict-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632"},
-    {file = "multidict-5.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952"},
-    {file = "multidict-5.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79"},
-    {file = "multidict-5.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456"},
-    {file = "multidict-5.1.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7"},
-    {file = "multidict-5.1.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635"},
-    {file = "multidict-5.1.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a"},
-    {file = "multidict-5.1.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea"},
-    {file = "multidict-5.1.0-cp38-cp38-win32.whl", hash = "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656"},
-    {file = "multidict-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3"},
-    {file = "multidict-5.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93"},
-    {file = "multidict-5.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647"},
-    {file = "multidict-5.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d"},
-    {file = "multidict-5.1.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8"},
-    {file = "multidict-5.1.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1"},
-    {file = "multidict-5.1.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841"},
-    {file = "multidict-5.1.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda"},
-    {file = "multidict-5.1.0-cp39-cp39-win32.whl", hash = "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"},
-    {file = "multidict-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359"},
-    {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"},
-]
 mypy-extensions = [
     {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
     {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
 ]
 nautobot = [
-    {file = "nautobot-1.0.0b3-py3-none-any.whl", hash = "sha256:9b83a84300dee5f687bbe83e17cca749082854f0def48f2dff48049a5f654760"},
-    {file = "nautobot-1.0.0b3.tar.gz", hash = "sha256:158434d841cac87d9a57b685a0005aa1d0264df9fbc057d14589a00bb2653481"},
+    {file = "nautobot-1.0.2-py3-none-any.whl", hash = "sha256:319ca74089a9b77829d559c2a8e8151aebb309c53a54cd08cb298bd573afbbdf"},
+    {file = "nautobot-1.0.2.tar.gz", hash = "sha256:a6d3ba1f8d87267e05035067a224d6a3c0e2d150f19219ee2691837f28ed7189"},
 ]
 nautobot-capacity-metrics = [
-    {file = "nautobot_capacity_metrics-1.0.0-py3-none-any.whl", hash = "sha256:9e0412ee3db2a5ee53e1fbc8996f47a2b8d15b3642a3e74485a788d269f35c6d"},
-    {file = "nautobot_capacity_metrics-1.0.0.tar.gz", hash = "sha256:becc2d356bc0dcf24fe239765955216fd46324ff2a40904d057c6857b5a3f938"},
+    {file = "nautobot-capacity-metrics-1.0.1.tar.gz", hash = "sha256:f6bdde11aedc63c233ad1468b7263a9bc5694938d0f197873fa0abc6a22c2ce5"},
+    {file = "nautobot_capacity_metrics-1.0.1-py3-none-any.whl", hash = "sha256:11a5d3686fafa34ddce1d643f54dbd4d205d3c76cb643617b774ff4beb0d3189"},
 ]
 nautobot-chatops = [
-    {file = "nautobot-chatops-1.1.0.tar.gz", hash = "sha256:c8610724591a0f35788a1e3c9b6a131afad3c9f2f86f4ad2c541979b026c27ff"},
-    {file = "nautobot_chatops-1.1.0-py3-none-any.whl", hash = "sha256:8f2248b32dc7f5ec34ae2386cd5eb692366dc3a5e586dbaf03ff98c953f83c5a"},
+    {file = "nautobot-chatops-1.3.1.tar.gz", hash = "sha256:3de491439f8e70d07753bea664ef7dea8781be196950b725b5af4861d63d5fc2"},
+    {file = "nautobot_chatops-1.3.1-py3-none-any.whl", hash = "sha256:824aa1190c34f28a7e021d020d58974bc6ec9291f78fa4702f892db67c0e6ce7"},
 ]
 netaddr = [
     {file = "netaddr-0.8.0-py2.py3-none-any.whl", hash = "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac"},
     {file = "netaddr-0.8.0.tar.gz", hash = "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"},
 ]
 oauthlib = [
-    {file = "oauthlib-3.1.0-py2.py3-none-any.whl", hash = "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"},
-    {file = "oauthlib-3.1.0.tar.gz", hash = "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889"},
+    {file = "oauthlib-3.1.1-py2.py3-none-any.whl", hash = "sha256:42bf6354c2ed8c6acb54d971fce6f88193d97297e18602a3a886603f9d7730cc"},
+    {file = "oauthlib-3.1.1.tar.gz", hash = "sha256:8f0215fcc533dd8dd1bee6f4c412d4f0cd7297307d43ac61666389e3bc3198a3"},
 ]
 packaging = [
     {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
@@ -1843,8 +1760,8 @@ pathspec = [
     {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"},
 ]
 pbr = [
-    {file = "pbr-5.5.1-py2.py3-none-any.whl", hash = "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"},
-    {file = "pbr-5.5.1.tar.gz", hash = "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9"},
+    {file = "pbr-5.6.0-py2.py3-none-any.whl", hash = "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"},
+    {file = "pbr-5.6.0.tar.gz", hash = "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd"},
 ]
 pillow = [
     {file = "Pillow-8.1.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:5cf03b9534aca63b192856aa601c68d0764810857786ea5da652581f3a44c2b0"},
@@ -1882,8 +1799,8 @@ pillow = [
     {file = "Pillow-8.1.2.tar.gz", hash = "sha256:b07c660e014852d98a00a91adfbe25033898a9d90a8f39beb2437d22a203fc44"},
 ]
 prometheus-client = [
-    {file = "prometheus_client-0.10.1-py2.py3-none-any.whl", hash = "sha256:030e4f9df5f53db2292eec37c6255957eb76168c6f974e4176c711cf91ed34aa"},
-    {file = "prometheus_client-0.10.1.tar.gz", hash = "sha256:b6c5a9643e3545bcbfd9451766cbaa5d9c67e7303c7bc32c750b6fa70ecb107d"},
+    {file = "prometheus_client-0.11.0-py2.py3-none-any.whl", hash = "sha256:b014bc76815eb1399da8ce5fc84b7717a3e63652b0c0f8804092c9363acab1b2"},
+    {file = "prometheus_client-0.11.0.tar.gz", hash = "sha256:3a8baade6cb80bcfe43297e33e7623f3118d660d41387593758e2fb1ea173a86"},
 ]
 promise = [
     {file = "promise-2.3.tar.gz", hash = "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0"},
@@ -1966,16 +1883,16 @@ pydocstyle = [
     {file = "pydocstyle-5.1.1.tar.gz", hash = "sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325"},
 ]
 pyjwt = [
-    {file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"},
-    {file = "PyJWT-1.7.1.tar.gz", hash = "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"},
+    {file = "PyJWT-2.1.0-py3-none-any.whl", hash = "sha256:934d73fbba91b0483d3857d1aff50e96b2a892384ee2c17417ed3203f173fca1"},
+    {file = "PyJWT-2.1.0.tar.gz", hash = "sha256:fba44e7898bbca160a2b2b501f492824fc8382485d3a6f11ba5d0c1937ce6130"},
 ]
 pylint = [
-    {file = "pylint-2.7.4-py3-none-any.whl", hash = "sha256:209d712ec870a0182df034ae19f347e725c1e615b2269519ab58a35b3fcbbe7a"},
-    {file = "pylint-2.7.4.tar.gz", hash = "sha256:bd38914c7731cdc518634a8d3c5585951302b6e2b6de60fbb3f7a0220e21eeee"},
+    {file = "pylint-2.8.3-py3-none-any.whl", hash = "sha256:792b38ff30903884e4a9eab814ee3523731abd3c463f3ba48d7b627e87013484"},
+    {file = "pylint-2.8.3.tar.gz", hash = "sha256:0a049c5d47b629d9070c3932d13bff482b12119b6a241a93bc460b0be16953c8"},
 ]
 pylint-django = [
-    {file = "pylint-django-2.4.3.tar.gz", hash = "sha256:a5a4515209a6237d1d390a4a307d53f53baaf4f058ecf4bb556c775d208f6b0d"},
-    {file = "pylint_django-2.4.3-py3-none-any.whl", hash = "sha256:dc5ed27bb7662d73444ccd15a0b3964ed6ced6cc2712b85db616102062d2ec35"},
+    {file = "pylint-django-2.4.4.tar.gz", hash = "sha256:f63f717169b0c2e4e19c28f1c32c28290647330184fcb7427805ae9b6994f3fc"},
+    {file = "pylint_django-2.4.4-py3-none-any.whl", hash = "sha256:aff49d9602a39c027b4ed7521a041438893205918f405800063b7ff692b7371b"},
 ]
 pylint-plugin-utils = [
     {file = "pylint-plugin-utils-0.6.tar.gz", hash = "sha256:57625dcca20140f43731311cd8fd879318bf45a8b0fd17020717a8781714a25a"},
@@ -2019,18 +1936,26 @@ pyyaml = [
     {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
     {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
     {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"},
     {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
     {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
     {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
     {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"},
     {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
     {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
     {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
     {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
+    {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"},
+    {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"},
     {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
     {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
     {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
     {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
+    {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"},
+    {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"},
     {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
     {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
     {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
@@ -2100,12 +2025,12 @@ requests-toolbelt = [
     {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"},
 ]
 rq = [
-    {file = "rq-1.8.0-py2.py3-none-any.whl", hash = "sha256:d861f1e794b595ae83f7b919d0c56d7102bc18f4f7b71395446d60d70344577c"},
-    {file = "rq-1.8.0.tar.gz", hash = "sha256:2a253385bec82f0e723ad25fb547d3892900a94b3c3acc53514c4bd74897cb69"},
+    {file = "rq-1.8.1-py2.py3-none-any.whl", hash = "sha256:2c7f795a2c85d84125314e49853ed66ca9acf062e997ada16e259c1c284f525f"},
+    {file = "rq-1.8.1.tar.gz", hash = "sha256:feec0f914e69cae2380e2f46d82901e2abbd6fb09db320acb295f486a338f89f"},
 ]
 "ruamel.yaml" = [
-    {file = "ruamel.yaml-0.17.4-py3-none-any.whl", hash = "sha256:ac79fb25f5476e8e9ed1c53b8a2286d2c3f5dde49eb37dbcee5c7eb6a8415a22"},
-    {file = "ruamel.yaml-0.17.4.tar.gz", hash = "sha256:44bc6b54fddd45e4bc0619059196679f9e8b79c027f4131bb072e6a22f4d5e28"},
+    {file = "ruamel.yaml-0.17.9-py3-none-any.whl", hash = "sha256:8873a6f5516e0d848c92418b0b006519c0566b6cd0dcee7deb9bf399e2bd204f"},
+    {file = "ruamel.yaml-0.17.9.tar.gz", hash = "sha256:374373b4743aee9f6d9f40bea600fe020a7ac7ae36b838b4a6a93f72b584a14c"},
 ]
 "ruamel.yaml.clib" = [
     {file = "ruamel.yaml.clib-0.2.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:28116f204103cb3a108dfd37668f20abe6e3cafd0d3fd40dba126c732457b3cc"},
@@ -2145,16 +2070,16 @@ rx = [
     {file = "Rx-1.6.1.tar.gz", hash = "sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23"},
 ]
 singledispatch = [
-    {file = "singledispatch-3.6.1-py2.py3-none-any.whl", hash = "sha256:85c97f94c8957fa4e6dab113156c182fb346d56d059af78aad710bced15f16fb"},
-    {file = "singledispatch-3.6.1.tar.gz", hash = "sha256:58b46ce1cc4d43af0aac3ac9a047bdb0f44e05f0b2fa2eec755863331700c865"},
+    {file = "singledispatch-3.6.2-py2.py3-none-any.whl", hash = "sha256:0d428477703d8386eb6aeed6e522c9f22d49f4363cdf4ed6a2ba3dc276053e20"},
+    {file = "singledispatch-3.6.2.tar.gz", hash = "sha256:d5bb9405a4b8de48e36709238e8b91b4f6f300f81a5132ba2531a9a738eca391"},
 ]
 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"},
 ]
-slackclient = [
-    {file = "slackclient-2.9.3-py2.py3-none-any.whl", hash = "sha256:2d68d668c02f4038299897e5c4723ab85dd40a3548354924b24f333a435856f8"},
-    {file = "slackclient-2.9.3.tar.gz", hash = "sha256:07ec8fa76f6aa64852210ae235ff9e637ba78124e06c0b07a7eeea4abb955965"},
+slack-sdk = [
+    {file = "slack_sdk-3.6.0-py2.py3-none-any.whl", hash = "sha256:e1b257923a1ef88b8620dd3abff94dc5b3eee16ef37975d101ba9e60123ac3af"},
+    {file = "slack_sdk-3.6.0.tar.gz", hash = "sha256:195f044e02a2844579a7a26818ce323e85dde8de224730c859644918d793399e"},
 ]
 smmap = [
     {file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"},
@@ -2170,8 +2095,8 @@ social-auth-app-django = [
     {file = "social_auth_app_django-4.0.0-py3-none-any.whl", hash = "sha256:567ad0e028311541d7dfed51d3bf2c60440a6fd236d5d4d06c5a618b3d6c57c5"},
 ]
 social-auth-core = [
-    {file = "social-auth-core-4.0.2.tar.gz", hash = "sha256:604798ec00a49a84f7a173460c5997c5409080e64acc8a47e2eefd6f59439a15"},
-    {file = "social_auth_core-4.0.2-py3-none-any.whl", hash = "sha256:2cc99a62ed884d3ef923b027baa9b0ef20554209214c9647cdb0012f69ba1223"},
+    {file = "social-auth-core-4.1.0.tar.gz", hash = "sha256:5ab43b3b15dce5f059db69cc3082c216574739f0edbc98629c8c6e8769c67eb4"},
+    {file = "social_auth_core-4.1.0-py3-none-any.whl", hash = "sha256:983b53167ac56e7ba4909db555602a6e7a98c97ca47183bb222eb85ba627bf2b"},
 ]
 sqlparse = [
     {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"},
@@ -2234,9 +2159,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"},
 ]
 uritemplate = [
     {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"},
@@ -2256,45 +2181,6 @@ wrapt = [
 yamllint = [
     {file = "yamllint-1.26.1.tar.gz", hash = "sha256:87d9462b3ed7e9dfa19caa177f7a77cd9888b3dc4044447d6ae0ab233bcd1324"},
 ]
-yarl = [
-    {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"},
-    {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"},
-    {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6"},
-    {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e"},
-    {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406"},
-    {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76"},
-    {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366"},
-    {file = "yarl-1.6.3-cp36-cp36m-win32.whl", hash = "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721"},
-    {file = "yarl-1.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643"},
-    {file = "yarl-1.6.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e"},
-    {file = "yarl-1.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3"},
-    {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8"},
-    {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a"},
-    {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c"},
-    {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f"},
-    {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970"},
-    {file = "yarl-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e"},
-    {file = "yarl-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50"},
-    {file = "yarl-1.6.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2"},
-    {file = "yarl-1.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec"},
-    {file = "yarl-1.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"},
-    {file = "yarl-1.6.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc"},
-    {file = "yarl-1.6.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959"},
-    {file = "yarl-1.6.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2"},
-    {file = "yarl-1.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2"},
-    {file = "yarl-1.6.3-cp38-cp38-win32.whl", hash = "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896"},
-    {file = "yarl-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a"},
-    {file = "yarl-1.6.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e"},
-    {file = "yarl-1.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724"},
-    {file = "yarl-1.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c"},
-    {file = "yarl-1.6.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25"},
-    {file = "yarl-1.6.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96"},
-    {file = "yarl-1.6.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0"},
-    {file = "yarl-1.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4"},
-    {file = "yarl-1.6.3-cp39-cp39-win32.whl", hash = "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424"},
-    {file = "yarl-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6"},
-    {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"},
-]
 zipp = [
     {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"},
     {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"},
diff --git a/pyproject.toml b/pyproject.toml
index dda126e..7d368f5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,8 +1,8 @@
 [tool.poetry]
-name = "nautobot-plugin-chatops-ansible"
+name = "nautobot-chatops-extension-ansible"
 version = "0.0.1"
 description = "Nautobot Chatpops Ansible Tower integration"
-authors = ["Network to Code, LLC <info@networktocode.com>"]
+authors = ["Network to Code, LLC <opensource@networktocode.com>"]
 
 readme = "README.md"
 homepage = "https://github.com/networktocode-llc/nautobot-plugin-chatops-ansible"
@@ -12,30 +12,31 @@ include = [
     "README.md",
 ]
 packages = [
-    { include = "nautobot_plugin_chatops_ansible" },
+    { include = "nautobot_chatops_extension_ansible" },
 ]
 
 [tool.poetry.plugins."nautobot.workers"]
-"ansible" = "nautobot_plugin_chatops_ansible.worker:ansible"
+"ansible" = "nautobot_chatops_extension_ansible.worker:ansible"
 
 [tool.poetry.dependencies]
 python = "^3.6"
-invoke = "^1.4.1"
 nautobot-chatops = "^1.1.0"
 requests = "^2.25.1"
 
 [tool.poetry.dev-dependencies]
 black = "^20.8b1"
-yamllint = "^1.23.0"
 bandit = "^1.7.0"
+coverage = "~5.4"
+invoke = "^1.4.1"
 pylint = "^2.5.2"
 pylint-django = "^2.0.15"
 pydocstyle = "^5.0.2"
 requests-mock = "^1.9.3"
+yamllint = "^1.23.0"
 
 [tool.black]
 line-length = 120
-target-version = ['py36']
+target-version = ['py37']
 include = '\.pyi?$'
 exclude = '''
 (
@@ -56,8 +57,8 @@ exclude = '''
 )
 '''
 [build-system]
-requires = ["poetry>=0.12"]
-build-backend = "poetry.masonry.api"
+requires = ["poetry_core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
 
 [tool.pylint.master]
 # Include the pylint_django plugin to avoid spurious warnings about Django patterns
@@ -76,7 +77,6 @@ disable = """
     too-few-public-methods,
     too-many-ancestors,
     too-many-instance-attributes,
-    duplicate-code
     """
 
 [tool.pylint.miscellaneous]
@@ -84,4 +84,4 @@ disable = """
 notes = """,
     FIXME,
     XXX,
-    """
\ No newline at end of file
+    """
diff --git a/tasks.py b/tasks.py
index 73104ec..e07369a 100644
--- a/tasks.py
+++ b/tasks.py
@@ -1,329 +1,376 @@
-"""Tasks for use with Invoke."""
-
+"""Tasks for use with Invoke.
+
+(c) 2020-2021 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 distutils.util import strtobool
+from invoke import Collection, task as invoke_task
 import os
-from invoke import task
 
-PYTHON_VER = os.getenv("PYTHON_VER", "3.7")
-NAUTOBOT_VER = os.getenv("NAUTOBOT_VER", "main")
 
-# Name of the docker image/container
-NAME = os.getenv("IMAGE_NAME", "nautobot-plugin-chatops-ansible")
-PWD = os.getcwd()
+def is_truthy(arg):
+    """Convert "truthy" strings into Booleans.
 
-COMPOSE_FILE = "development/docker-compose.yml"
-BUILD_NAME = "nautobot_plugin_chatops_ansible"
+    Examples:
+        >>> is_truthy('yes')
+        True
+    Args:
+        arg (str): Truthy string (True values are y, yes, t, true, on and 1; false values are n, no,
+        f, false, off and 0. Raises ValueError if val is anything else.
+    """
+    if isinstance(arg, bool):
+        return arg
+    return bool(strtobool(arg))
+
+
+# Use pyinvoke configuration for default values, see http://docs.pyinvoke.org/en/stable/concepts/configuration.html
+# Variables may be overwritten in invoke.yml or by the environment variables INVOKE_NAUTOBOT_CHATOPS_EXTENSION_ANSIBLE_xxx
+namespace = Collection("nautobot_chatops_extension_ansible")
+namespace.configure(
+    {
+        "nautobot_chatops_extension_ansible": {
+            "nautobot_ver": "1.0.1",
+            "project_name": "nautobot-chatops-extension-ansible",
+            "python_ver": "3.6",
+            "local": False,
+            "compose_dir": os.path.join(os.path.dirname(__file__), "development"),
+            "compose_files": [
+                "docker-compose.requirements.yml",
+                "docker-compose.base.yml",
+                "docker-compose.dev.yml",
+                "docker-compose.docs.yml",
+            ],
+        }
+    }
+)
+
+
+def task(function=None, *args, **kwargs):
+    """Task decorator to override the default Invoke task decorator and add each task to the invoke namespace."""
+
+    def task_wrapper(function=None):
+        """Wrapper around invoke.task to add the task to the namespace as well."""
+        if args or kwargs:
+            task_func = invoke_task(*args, **kwargs)(function)
+        else:
+            task_func = invoke_task(function)
+        namespace.add_task(task_func)
+        return task_func
+
+    if function:
+        # The decorator was called with no arguments
+        return task_wrapper(function)
+    # The decorator was called with arguments
+    return task_wrapper
+
+
+def docker_compose(context, command, **kwargs):
+    """Helper function for running a specific docker-compose command with all appropriate parameters and environment.
+
+    Args:
+        context (obj): Used to run specific commands
+        command (str): Command string to append to the "docker-compose ..." command, such as "build", "up", etc.
+        **kwargs: Passed through to the context.run() call.
+    """
+    build_env = {
+        "NAUTOBOT_VER": context.nautobot_chatops_extension_ansible.nautobot_ver,
+        "PYTHON_VER": context.nautobot_chatops_extension_ansible.python_ver,
+    }
+    compose_command = f'docker-compose --project-name {context.nautobot_chatops_extension_ansible.project_name} --project-directory "{context.nautobot_chatops_extension_ansible.compose_dir}"'
+    for compose_file in context.nautobot_chatops_extension_ansible.compose_files:
+        compose_file_path = os.path.join(context.nautobot_chatops_extension_ansible.compose_dir, compose_file)
+        compose_command += f' -f "{compose_file_path}"'
+    compose_command += f" {command}"
+    print(f'Running docker-compose command "{command}"')
+    return context.run(compose_command, env=build_env, **kwargs)
+
+
+def run_command(context, command, **kwargs):
+    """Wrapper to run a command locally or inside the nautobot container."""
+    if is_truthy(context.nautobot_chatops_extension_ansible.local):
+        context.run(command, **kwargs)
+    else:
+        # Check if netbox is running, no need to start another netbox container to run a command
+        docker_compose_status = "ps --services --filter status=running"
+        results = docker_compose(context, docker_compose_status, hide="out")
+        if "nautobot" in results.stdout:
+            compose_command = f"exec nautobot {command}"
+        else:
+            compose_command = f"run --entrypoint '{command}' nautobot"
+
+        docker_compose(context, compose_command, pty=True)
 
 
 # ------------------------------------------------------------------------------
 # BUILD
 # ------------------------------------------------------------------------------
-@task
-def build(context, nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Build all docker images.
+@task(
+    help={
+        "force_rm": "Always remove intermediate containers",
+        "cache": "Whether to use Docker's cache when building the image (defaults to enabled)",
+    }
+)
+def build(context, force_rm=False, cache=True):
+    """Build Nautobot docker image."""
+    command = "build"
 
-    Args:
-        context (obj): Used to run specific commands
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
-    """
-    context.run(
-        f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} build --build-arg nautobot_ver={nautobot_ver} --build-arg python_ver={python_ver}",
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-    )
+    if not cache:
+        command += " --no-cache"
+    if force_rm:
+        command += " --force-rm"
 
+    print(f"Building Nautobot with Python {context.nautobot_chatops_extension_ansible.python_ver}...")
+    docker_compose(context, command)
 
-@task
-def generate_packages(context, nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Generate all Python packages inside docker and copy the file locally under dist/.
 
-    Args:
-        context (obj): Used to run specific commands
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
-    """
-    container_name = f"{BUILD_NAME}_nautobot_package"
-    context.run(
-        f"docker rm {container_name} || true",
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-        pty=True,
-    )
-    context.run(
-        f"docker-compose  -f {COMPOSE_FILE} -p {BUILD_NAME} run --name {container_name} -w /source nautobot poetry build",
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-    )
-    context.run(
-        f"docker cp {container_name}:/source/dist .",
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-        pty=True,
-    )
+@task
+def generate_packages(context):
+    """Generate all Python packages inside docker and copy the file locally under dist/."""
+    command = "poetry build"
+    run_command(context, command)
 
 
 # ------------------------------------------------------------------------------
 # START / STOP / DEBUG
 # ------------------------------------------------------------------------------
 @task
-def debug(context, nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Start Nautobot and its dependencies in debug mode.
-
-    Args:
-        context (obj): Used to run specific commands
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
-    """
-    print("Starting Nautobot .. ")
-    context.run(
-        f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} up",
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-    )
+def debug(context):
+    """Start Nautobot and its dependencies in debug mode."""
+    print("Starting Nautobot in debug mode...")
+    docker_compose(context, "up")
 
 
 @task
-def start(context, nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Start Nautobot and its dependencies in detached mode.
+def start(context):
+    """Start Nautobot and its dependencies in detached mode."""
+    print("Starting Nautobot in detached mode...")
+    docker_compose(context, "up --detach")
 
-    Args:
-        context (obj): Used to run specific commands
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
-    """
-    print("Starting Nautobot in detached mode.. ")
-    context.run(
-        f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} up -d",
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-    )
+
+@task
+def restart(context):
+    """Gracefully restart all containers."""
+    print("Restarting Nautobot...")
+    docker_compose(context, "restart")
 
 
 @task
-def stop(context, nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Stop Nautobot and its dependencies.
+def stop(context):
+    """Stop Nautobot and its dependencies."""
+    print("Stopping Nautobot...")
+    docker_compose(context, "down")
 
-    Args:
-        context (obj): Used to run specific commands
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
-    """
-    print("Stopping Nautobot .. ")
-    context.run(
-        f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} down",
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-    )
+
+@task
+def destroy(context):
+    """Destroy all containers and volumes."""
+    print("Destroying Nautobot...")
+    docker_compose(context, "down --volumes")
 
 
 @task
-def destroy(context, nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Destroy all containers and volumes.
+def vscode(context):
+    """Launch Visual Studio Code with the appropriate Environment variables to run in a container."""
+    command = "code nautobot.code-workspace"
 
-    Args:
-        context (obj): Used to run specific commands
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
-    """
-    context.run(
-        f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} down",
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-    )
-    context.run(
-        f"docker volume rm -f {BUILD_NAME}_pgdata_nautobot_plugin_chatops_ansible",
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-    )
+    context.run(command)
 
 
 # ------------------------------------------------------------------------------
 # ACTIONS
 # ------------------------------------------------------------------------------
 @task
-def nbshell(context, nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Launch a nbshell session.
-
-    Args:
-        context (obj): Used to run specific commands
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
-    """
-    context.run(
-        f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} run nautobot nautobot-server nbshell",
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-        pty=True,
-    )
+def nbshell(context):
+    """Launch an interactive nbshell session."""
+    command = "nautobot-server nbshell"
+    run_command(context, command)
 
 
 @task
-def cli(context, nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Launch a bash shell inside the running Nautobot container.
+def cli(context):
+    """Launch a bash shell inside the running Nautobot container."""
+    run_command(context, "bash")
 
-    Args:
-        context (obj): Used to run specific commands
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
-    """
-    context.run(
-        f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} run nautobot bash",
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-        pty=True,
-    )
 
+@task(
+    help={
+        "user": "name of the superuser to create (default: admin)",
+    }
+)
+def createsuperuser(context, user="admin"):
+    """Create a new Nautobot superuser account (default: "admin"), will prompt for password."""
+    command = f"nautobot-server createsuperuser --username {user}"
 
-@task
-def create_user(context, user="admin", nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Create a new user in django (default: admin), will prompt for password.
+    run_command(context, command)
 
-    Args:
-        context (obj): Used to run specific commands
-        user (str): name of the superuser to create
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
-    """
-    context.run(
-        f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} run nautobot nautobot-server createsuperuser --username {user}",
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-        pty=True,
-    )
+
+@task(
+    help={
+        "name": "name of the migration to be created; if unspecified, will autogenerate a name",
+    }
+)
+def makemigrations(context, name=""):
+    """Perform makemigrations operation in Django."""
+    command = "nautobot-server makemigrations nautobot_chatops_extension_ansible"
+
+    if name:
+        command += f" --name {name}"
+
+    run_command(context, command)
 
 
 @task
-def makemigrations(context, name="", nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Run Make Migration in Django.
+def migrate(context):
+    """Perform migrate operation in Django."""
+    command = "nautobot-server migrate"
 
-    Args:
-        context (obj): Used to run specific commands
-        name (str): Name of the migration to be created
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
+    run_command(context, command)
+
+
+@task(help={})
+def post_upgrade(context):
     """
-    context.run(
-        f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} up -d postgres",
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-    )
+    Performs Nautobot common post-upgrade operations using a single entrypoint.
 
-    if name:
-        context.run(
-            f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} run nautobot nautobot-server makemigrations --name {name}",
-            env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-        )
-    else:
-        context.run(
-            f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} run nautobot nautobot-server makemigrations",
-            env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-        )
+    This will run the following management commands with default settings, in order:
+
+    - migrate
+    - trace_paths
+    - collectstatic
+    - remove_stale_contenttypes
+    - clearsessions
+    - invalidate all
+    """
+    command = "nautobot-server post_upgrade"
 
-    context.run(
-        f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} down",
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-    )
+    run_command(context, command)
 
 
 # ------------------------------------------------------------------------------
-# TESTS / LINTING
+# TESTS
 # ------------------------------------------------------------------------------
-@task
-def unittest(context, nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Run Django unit tests for the plugin.
+@task(
+    help={
+        "autoformat": "Apply formatting recommendations automatically, rather than failing if formatting is incorrect.",
+    }
+)
+def black(context, autoformat=False):
+    """Check Python code style with Black."""
+    if autoformat:
+        black_command = "black"
+    else:
+        black_command = "black --check --diff"
 
-    Args:
-        context (obj): Used to run specific commands
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
-    """
-    docker = f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} run nautobot"
-    context.run(
-        f'{docker} sh -c "nautobot-server test nautobot_plugin_chatops_ansible"',
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-        pty=True,
-    )
+    command = f"{black_command} ."
 
+    run_command(context, command)
 
-@task
-def pylint(context, nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Run pylint code analysis.
 
-    Args:
-        context (obj): Used to run specific commands
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
-    """
-    docker = f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} run nautobot"
-    # We exclude the /migrations/ directory since it is autogenerated code
-    context.run(
-        f"{docker} sh -c \"cd /source && find . -name '*.py' -not -path '*/migrations/*' | "
-        'PYTHONPATH=/opt/nautobot xargs pylint"',
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-        pty=True,
-    )
+@task
+def flake8(context):
+    """Check for PEP8 compliance and other style issues."""
+    command = "flake8 ."
+    run_command(context, command)
 
 
 @task
-def black(context, nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Run black to check that Python files adhere to its style standards.
-
-    Args:
-        context (obj): Used to run specific commands
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
-    """
-    docker = f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} run nautobot"
-    context.run(
-        f'{docker} sh -c "cd /source && black --check --diff ."',
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-        pty=True,
-    )
+def hadolint(context):
+    """Check Dockerfile for hadolint compliance and other style issues."""
+    command = "hadolint development/Dockerfile"
+    run_command(context, command)
 
 
 @task
-def pydocstyle(context, nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Run pydocstyle to validate docstring formatting adheres to NTC defined standards.
+def pylint(context):
+    """Run pylint code analysis."""
+    command = 'pylint --init-hook "import nautobot; nautobot.setup()" --rcfile pyproject.toml nautobot_chatops_extension_ansible'
+    run_command(context, command)
 
-    Args:
-        context (obj): Used to run specific commands
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
-    """
-    docker = f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} run nautobot"
+
+@task
+def pydocstyle(context):
+    """Run pydocstyle to validate docstring formatting adheres to NTC defined standards."""
     # We exclude the /migrations/ directory since it is autogenerated code
-    context.run(
-        f"{docker} sh -c \"cd /source && find . -name '*.py' -not -path '*/migrations/*' | xargs pydocstyle\"",
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-        pty=True,
-    )
+    command = "pydocstyle --config=.pydocstyle.ini ."
+    run_command(context, command)
 
 
 @task
-def bandit(context, nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Run bandit to validate basic static code security analysis.
-
-    Args:
-        context (obj): Used to run specific commands
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
-    """
-    docker = f"docker-compose -f {COMPOSE_FILE} -p {BUILD_NAME} run nautobot"
-    context.run(
-        f'{docker} sh -c "cd /source && bandit --recursive ./ --configfile .bandit.yml"',
-        env={"NAUTOBOT_VER": nautobot_ver, "PYTHON_VER": python_ver},
-        pty=True,
-    )
+def bandit(context):
+    """Run bandit to validate basic static code security analysis."""
+    command = "bandit --recursive . --configfile .bandit.yml"
+    run_command(context, command)
 
 
 @task
-def tests(context, nautobot_ver=NAUTOBOT_VER, python_ver=PYTHON_VER):
-    """Run all tests for this plugin.
+def check_migrations(context):
+    """Check for missing migrations."""
+    command = "nautobot-server --config=nautobot/core/tests/nautobot_config.py makemigrations --dry-run --check"
+
+    run_command(context, command)
+
+
+@task(
+    help={
+        "keepdb": "save and re-use test database between test runs for faster re-testing.",
+        "label": "specify a directory or module to test instead of running all Nautobot tests",
+        "failfast": "fail as soon as a single test fails don't run the entire test suite",
+        "buffer": "Discard output from passing tests",
+    }
+)
+def unittest(context, keepdb=False, label="nautobot_chatops_extension_ansible", failfast=False, buffer=True):
+    """Run Nautobot unit tests."""
+    command = f"coverage run --module nautobot.core.cli test {label}"
+
+    if keepdb:
+        command += " --keepdb"
+    if failfast:
+        command += " --failfast"
+    if buffer:
+        command += " --buffer"
+    run_command(context, command)
 
-    Args:
-        context (obj): Used to run specific commands
-        nautobot_ver (str): Nautobot version to use to build the container
-        python_ver (str): Will use the Python version docker image to build from
-    """
+
+@task
+def unittest_coverage(context):
+    """Report on code test coverage as measured by 'invoke unittest'."""
+    command = "coverage report --skip-covered --include 'nautobot_chatops_extension_ansible/*' --omit *migrations*"
+
+    run_command(context, command)
+
+
+@task(
+    help={
+        "failfast": "fail as soon as a single test fails don't run the entire test suite",
+    }
+)
+def tests(context, failfast=False):
+    """Run all tests for this plugin."""
+    # If we are not running locally, start the docker containers so we don't have to for each test
+    if not is_truthy(context.nautobot_chatops_extension_ansible.local):
+        print("Starting Docker Containers...")
+        start(context)
     # Sorted loosely from fastest to slowest
     print("Running black...")
-    black(context, nautobot_ver=nautobot_ver, python_ver=python_ver)
+    black(context)
+    print("Running flake8...")
+    flake8(context)
     print("Running bandit...")
-    bandit(context, nautobot_ver=nautobot_ver, python_ver=python_ver)
+    bandit(context)
     print("Running pydocstyle...")
-    pydocstyle(context, nautobot_ver=nautobot_ver, python_ver=python_ver)
-    # print("Running pylint...")
-    # pylint(context, nautobot_ver=nautobot_ver, python_ver=python_ver)
+    pydocstyle(context)
+    print("Running pylint...")
+    pylint(context)
     print("Running unit tests...")
-    unittest(context, nautobot_ver=nautobot_ver, python_ver=python_ver)
-    # print("Running yamllint...")
-    # yamllint(context, NAME, python_ver)
-
+    unittest(context, failfast=failfast)
     print("All tests have passed!")
+    unittest_coverage(context)


Test patch:
diff --git a/nautobot_chatops_extension_ansible/tests/__init__.py b/nautobot_chatops_extension_ansible/tests/__init__.py
new file mode 100644
index 0000000..7e0a59a
--- /dev/null
+++ b/nautobot_chatops_extension_ansible/tests/__init__.py
@@ -0,0 +1,1 @@
+"""Unit tests for nautobot_chatops_extension_ansible plugin."""
diff --git a/nautobot_plugin_chatops_ansible/tests/fixtures/01_get_inventories.json b/nautobot_chatops_extension_ansible/tests/fixtures/01_get_inventories.json
similarity index 100%
rename from nautobot_plugin_chatops_ansible/tests/fixtures/01_get_inventories.json
rename to nautobot_chatops_extension_ansible/tests/fixtures/01_get_inventories.json
diff --git a/nautobot_plugin_chatops_ansible/tests/fixtures/02_get_inventory_groups.json b/nautobot_chatops_extension_ansible/tests/fixtures/02_get_inventory_groups.json
similarity index 100%
rename from nautobot_plugin_chatops_ansible/tests/fixtures/02_get_inventory_groups.json
rename to nautobot_chatops_extension_ansible/tests/fixtures/02_get_inventory_groups.json
diff --git a/nautobot_plugin_chatops_ansible/tests/fixtures/03_get_inventory_hosts.json b/nautobot_chatops_extension_ansible/tests/fixtures/03_get_inventory_hosts.json
similarity index 100%
rename from nautobot_plugin_chatops_ansible/tests/fixtures/03_get_inventory_hosts.json
rename to nautobot_chatops_extension_ansible/tests/fixtures/03_get_inventory_hosts.json
diff --git a/nautobot_plugin_chatops_ansible/tests/fixtures/04_get_dashboard.json b/nautobot_chatops_extension_ansible/tests/fixtures/04_get_dashboard.json
similarity index 100%
rename from nautobot_plugin_chatops_ansible/tests/fixtures/04_get_dashboard.json
rename to nautobot_chatops_extension_ansible/tests/fixtures/04_get_dashboard.json
diff --git a/nautobot_plugin_chatops_ansible/tests/fixtures/05_get_jobs.json b/nautobot_chatops_extension_ansible/tests/fixtures/05_get_jobs.json
similarity index 100%
rename from nautobot_plugin_chatops_ansible/tests/fixtures/05_get_jobs.json
rename to nautobot_chatops_extension_ansible/tests/fixtures/05_get_jobs.json
diff --git a/nautobot_plugin_chatops_ansible/tests/fixtures/06_get_projects.json b/nautobot_chatops_extension_ansible/tests/fixtures/06_get_projects.json
similarity index 100%
rename from nautobot_plugin_chatops_ansible/tests/fixtures/06_get_projects.json
rename to nautobot_chatops_extension_ansible/tests/fixtures/06_get_projects.json
diff --git a/nautobot_plugin_chatops_ansible/tests/test_tower.py b/nautobot_chatops_extension_ansible/tests/test_tower.py
similarity index 100%
rename from nautobot_plugin_chatops_ansible/tests/test_tower.py
rename to nautobot_chatops_extension_ansible/tests/test_tower.py
diff --git a/nautobot_plugin_chatops_ansible/tests/__init__.py b/nautobot_plugin_chatops_ansible/tests/__init__.py
deleted file mode 100644
index bed922d..0000000
--- a/nautobot_plugin_chatops_ansible/tests/__init__.py
+++ /dev/null
@@ -1,1 +0,0 @@
-"""Unit tests for nautobot_plugin_chatops_ansible plugin."""