From 718b18ec1615434eae322238d926c264bd6d957b Mon Sep 17 00:00:00 2001 From: Jan Snasel Date: Wed, 13 Sep 2023 15:57:44 +0200 Subject: [PATCH] Cookie initialy baked by NetworkToCode Cookie Drift Manager Tool (#583) * Cookie initialy baked by NetworkToCode Cookie Drift Manager Tool Template: ``` { "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", "dir": "nautobot-app", "ref": "develop", "path": null } ``` Cookie: ``` { "remote": "https://github.com/nautobot/nautobot-plugin-golden-config.git", "path": "/opt/ntc/drift-manager/outputs/nautobot-plugin-golden-config", "repository_path": "/opt/ntc/drift-manager/outputs/nautobot-plugin-golden-config", "dir": "", "branch_prefix": "drift-manager", "context": { "codeowner_github_usernames": "@itdependsnetworks @jeffkala @nkallergis", "full_name": "Network to Code, LLC", "email": "opensource@networktocode.com", "github_org": "nautobot", "plugin_name": "nautobot_golden_config", "verbose_name": "Golden Config", "plugin_slug": "nautobot-golden-config", "project_slug": "nautobot-plugin-golden-config", "repo_url": "https://github.com/nautobot/nautobot-plugin-golden-config", "base_url": "golden-config", "min_nautobot_version": "1.4.0", "max_nautobot_version": "1.9999", "nautobot_version": "latest", "camel_name": "NautobotGoldenConfig", "project_short_description": "A plugin for configuration on nautobot", "version": "1.0.0", "model_class_name": "None", "open_source_license": "Apache-2.0", "docs_base_url": "https://docs.nautobot.com", "docs_app_url": "https://docs.nautobot.com/projects/golden-config/en/latest", "_template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", "_output_dir": "/opt/ntc/drift-manager/outputs", "_repo_dir": "/opt/ntc/drift-manager/outputs/.cookiecutters/cookiecutter-nautobot-app/nautobot-app" }, "base_branch": "next-2.0", "remote_name": "origin", "pull_request_strategy": "PullRequestStrategy.CREATE", "post_actions": [] } ``` CLI Arguments: ``` { "cookie_dir": "", "input": false, "json_filename": "./setup-cookie-golden-config.json", "output_dir": "./outputs", "push": true, "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", "template_dir": "nautobot-app", "template_ref": "develop", "pull_request": null, "post_action": [], "disable_post_actions": false } ``` * chore: Manual fixes * fix: Syntax error * fix: Black * chore: Move docs changes to separate pr #584 * fix: Update poetry lock * fix: Pylint * fix: PR comments * fix: Job invocation --------- Co-authored-by: bakebot --- .bandit.yml | 2 +- .cookiecutter.json | 33 ++ .dockerignore | 27 ++ .flake8 | 17 +- .github/CODEOWNERS | 2 +- .github/ISSUE_TEMPLATE/bug_report.md | 16 +- .github/ISSUE_TEMPLATE/feature_request.md | 4 +- .../pull_request_template.md | 10 + .github/workflows/ci.yml | 28 +- .github/workflows/rebake.yaml | 34 ++ .github/workflows/upstream_testing.yml | 6 +- .gitignore | 24 +- .yamllint.yml | 5 +- LICENSE | 2 +- development/Dockerfile | 29 +- development/development.env | 2 - development/docker-compose.base.yml | 4 +- development/docker-compose.dev.yml | 8 + development/docker-compose.mysql.yml | 8 +- development/docker-compose.postgres.yml | 4 +- development/nautobot_config.py | 84 ++--- invoke.example.yml | 2 +- invoke.mysql.yml | 2 +- nautobot_golden_config/__init__.py | 4 +- nautobot_golden_config/jobs.py | 11 +- .../nornir_plays/config_compliance.py | 3 +- nautobot_golden_config/tests/test_basic.py | 15 +- poetry.lock | 339 +++++++++++++++-- pyproject.toml | 35 +- tasks.py | 341 +++++++++++++++--- 30 files changed, 894 insertions(+), 207 deletions(-) create mode 100644 .cookiecutter.json create mode 100644 .dockerignore create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md create mode 100644 .github/workflows/rebake.yaml diff --git a/.bandit.yml b/.bandit.yml index 16ab010a..78140801 100644 --- a/.bandit.yml +++ b/.bandit.yml @@ -2,6 +2,6 @@ # No need to check for security issues in the test scripts! exclude_dirs: - "./nautobot_golden_config/tests/" - + - "./.venv/" skips: - "B404" diff --git a/.cookiecutter.json b/.cookiecutter.json new file mode 100644 index 00000000..ea65bfeb --- /dev/null +++ b/.cookiecutter.json @@ -0,0 +1,33 @@ +{ + "cookiecutter": { + "codeowner_github_usernames": "@itdependsnetworks @jeffkala @nkallergis", + "full_name": "Network to Code, LLC", + "email": "opensource@networktocode.com", + "github_org": "nautobot", + "plugin_name": "nautobot_golden_config", + "verbose_name": "Golden Config", + "plugin_slug": "nautobot-golden-config", + "project_slug": "nautobot-plugin-golden-config", + "repo_url": "https://github.com/nautobot/nautobot-plugin-golden-config", + "base_url": "golden-config", + "min_nautobot_version": "1.4.0", + "max_nautobot_version": "1.9999", + "nautobot_version": "latest", + "camel_name": "NautobotGoldenConfig", + "project_short_description": "A plugin for configuration on nautobot", + "version": "1.0.0", + "model_class_name": "None", + "open_source_license": "Apache-2.0", + "docs_base_url": "https://docs.nautobot.com", + "docs_app_url": "https://docs.nautobot.com/projects/golden-config/en/latest", + "_drift_manager": { + "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", + "template_dir": "nautobot-app", + "template_ref": "develop", + "cookie_dir": "", + "branch_prefix": "drift-manager", + "pull_request_strategy": "create", + "post_actions": [] + } + } +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..2270f496 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,27 @@ +# Docker related +development/Dockerfile +development/docker-compose*.yml +development/*.env +*.env +environments/ + +# Python +**/*.pyc +**/*.pyo +**/__pycache__/ +**/.pytest_cache/ +**/.venv/ + + +# Other +docs/_build +FAQ.md +.git/ +.gitignore +.github +tasks.py +LICENSE +**/*.log +**/.vscode/ +invoke*.yml +tasks.py diff --git a/.flake8 b/.flake8 index aaa63b60..c925648d 100644 --- a/.flake8 +++ b/.flake8 @@ -1,4 +1,15 @@ [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, F811, F401, F405, E203 +ignore = + 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 + # TODO: Review the following rules, not in the template + F811, + F401, + F405, + E203, +exclude = + migrations, + __pycache__, + manage.py, + settings.py, + .venv diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 30cca39a..d1982711 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ -# Default owners for all files in this repository +# Default owner(s) of all files in this repository * @itdependsnetworks @jeffkala @nkallergis diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 8f26fcbf..cbb194ae 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -5,9 +5,16 @@ about: Report a reproducible bug in the current release of nautobot-golden-confi ### Environment * Python version: -* Nautobot version: +* Nautobot version: * nautobot-golden-config version: + +### Expected Behavior + + + +### Observed Behavior + -### Expected Behavior - - - -### Observed Behavior diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 924a52ad..a8d54617 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,11 +1,11 @@ --- name: ✨ Feature Request about: Propose a new feature or enhancement + --- ### Environment -* Python version: -* Nautobot version: +* Nautobot version: * nautobot-golden-config version: Start safe to modify section -# Uncomment the line below if you are apt-installing any package. -# RUN apt update -# RUN apt install libldap2-dev +# Uncomment the lines below if you are apt-installing any package. +# RUN apt-get -y update && apt-get -y install \ +# libldap2-dev \ +# && rm -rf /var/lib/apt/lists/* # --> Stop safe to modify section # ------------------------------------------------------------------------------------- -# Install Nautobot Plugin +# Install Nautobot App # ------------------------------------------------------------------------------------- # !!! USE CAUTION WHEN MODIFYING LINES BELOW @@ -54,7 +53,7 @@ WORKDIR /source COPY . /source # Get container's installed Nautobot version as a forced constraint -# NAUTOBOT_VER may be a branch name and not a published release therefore we need to get the installed version +# NAUTOBOT_VER may be a branch name and not a published release therefor we need to get the installed version # so pip can use it to recognize local constraints. RUN pip show nautobot | grep "^Version: " | sed -e 's/Version: /nautobot==/' > constraints.txt @@ -68,7 +67,7 @@ RUN poetry export -f requirements.txt --with dev --without-hashes --output poetr RUN sort poetry_freeze_base.txt poetry_freeze_all.txt | uniq -u > poetry_freeze_dev.txt # Install all local project as editable, constrained on Nautobot version, to get any additional -# direct dependencies of the plugin +# direct dependencies of the app RUN pip install -c constraints.txt -e . # Install any dev dependencies frozen from Poetry diff --git a/development/development.env b/development/development.env index 080a7c71..6b7d03e2 100644 --- a/development/development.env +++ b/development/development.env @@ -7,8 +7,6 @@ NAUTOBOT_BANNER_TOP="Local" NAUTOBOT_CHANGELOG_RETENTION=0 NAUTOBOT_DEBUG=True -NAUTOBOT_DJANGO_EXTENSIONS_ENABLED=True -NAUTOBOT_DJANGO_TOOLBAR_ENABLED=True NAUTOBOT_LOG_LEVEL=DEBUG NAUTOBOT_METRICS_ENABLED=True NAUTOBOT_NAPALM_TIMEOUT=5 diff --git a/development/docker-compose.base.yml b/development/docker-compose.base.yml index 26356204..6a0a4c72 100644 --- a/development/docker-compose.base.yml +++ b/development/docker-compose.base.yml @@ -22,13 +22,13 @@ services: db: condition: "service_healthy" <<: - - *nautobot-build - *nautobot-base + - *nautobot-build worker: entrypoint: - "sh" - "-c" # this is to evaluate the $NAUTOBOT_LOG_LEVEL from the env - - "nautobot-server celery worker -l $$NAUTOBOT_LOG_LEVEL" ## $$ because of docker-compose + - "nautobot-server celery worker -l $$NAUTOBOT_LOG_LEVEL --events" ## $$ because of docker-compose depends_on: - "nautobot" healthcheck: diff --git a/development/docker-compose.dev.yml b/development/docker-compose.dev.yml index b95ecb81..8e8c15db 100644 --- a/development/docker-compose.dev.yml +++ b/development/docker-compose.dev.yml @@ -19,6 +19,8 @@ services: # - "../../nautobot-plugin-nornir/nautobot_plugin_nornir:/opt/nautobot/lib/python3.11/site-packages/nautobot_plugin_nornir" # - "../../nautobot/nautobot:/opt/nautobot/lib/python3.11/site-packages/nautobot" + healthcheck: + test: ["CMD", "true"] # Due to layering, disable: true won't work. Instead, change the test docs: entrypoint: "mkdocs serve -v -a 0.0.0.0:8080" ports: @@ -30,6 +32,10 @@ services: disable: true tty: true worker: + entrypoint: + - "sh" + - "-c" # this is to evaluate the $NAUTOBOT_LOG_LEVEL from the env + - "watchmedo auto-restart --directory './' --pattern '*.py' --recursive -- nautobot-server celery worker -l $$NAUTOBOT_LOG_LEVEL --events" ## $$ because of docker-compose volumes: - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" - "../:/source" @@ -39,6 +45,8 @@ services: # - "../../nornir-nautobot/nornir_nautobot:/opt/nautobot/lib/python3.11/site-packages/nornir_nautobot" # - "../../nautobot-plugin-nornir/nautobot_plugin_nornir:/opt/nautobot/lib/python3.11/site-packages/nautobot_plugin_nornir" # - "../../nautobot/nautobot:/opt/nautobot/lib/python3.11/site-packages/nautobot" + healthcheck: + test: ["CMD", "true"] # Due to layering, disable: true won't work. Instead, change the test # To expose postgres or redis to the host uncomment the following # postgres: # ports: diff --git a/development/docker-compose.mysql.yml b/development/docker-compose.mysql.yml index c7fa6a1f..062ada94 100644 --- a/development/docker-compose.mysql.yml +++ b/development/docker-compose.mysql.yml @@ -20,6 +20,7 @@ services: image: "mysql:8" command: - "--default-authentication-plugin=mysql_native_password" + - "--max_connections=1000" env_file: - "development.env" - "creds.env" @@ -27,7 +28,12 @@ services: volumes: - "mysql_data:/var/lib/mysql" healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + test: + - "CMD" + - "mysqladmin" + - "ping" + - "-h" + - "localhost" timeout: "20s" retries: 10 volumes: diff --git a/development/docker-compose.postgres.yml b/development/docker-compose.postgres.yml index 55afdb70..12d1de31 100644 --- a/development/docker-compose.postgres.yml +++ b/development/docker-compose.postgres.yml @@ -7,11 +7,13 @@ services: - "NAUTOBOT_DB_ENGINE=django.db.backends.postgresql" db: image: "postgres:13-alpine" + command: + - "-c" + - "max_connections=200" env_file: - "development.env" - "creds.env" volumes: - # - "./nautobot.sql:/tmp/nautobot.sql" - "postgres_data:/var/lib/postgresql/data" healthcheck: test: "pg_isready --username=$$POSTGRES_USER --dbname=$$POSTGRES_DB" diff --git a/development/nautobot_config.py b/development/nautobot_config.py index 546c4417..5599a096 100644 --- a/development/nautobot_config.py +++ b/development/nautobot_config.py @@ -1,13 +1,25 @@ """Nautobot development configuration file.""" -# pylint: disable=invalid-envvar-default import os import sys from django.utils.module_loading import import_string - -from nautobot.core.settings import * # noqa: F403 +from nautobot.core.settings import * # noqa: F403 # pylint: disable=wildcard-import,unused-wildcard-import from nautobot.core.settings_funcs import is_truthy, parse_redis_connection +# +# Debug +# + +DEBUG = is_truthy(os.getenv("NAUTOBOT_DEBUG", False)) +_TESTING = len(sys.argv) > 1 and sys.argv[1] == "test" + +if DEBUG and not _TESTING: + DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda _request: True} + + if "debug_toolbar" not in INSTALLED_APPS: # noqa: F405 + INSTALLED_APPS.append("debug_toolbar") # noqa: F405 + if "debug_toolbar.middleware.DebugToolbarMiddleware" not in MIDDLEWARE: # noqa: F405 + MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") # noqa: F405 # # Misc. settings @@ -16,6 +28,9 @@ ALLOWED_HOSTS = os.getenv("NAUTOBOT_ALLOWED_HOSTS", "").split(" ") SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "") +# +# Database +# nautobot_db_engine = os.getenv("NAUTOBOT_DB_ENGINE", "django.db.backends.postgresql") default_db_settings = { @@ -45,18 +60,28 @@ DATABASES["default"]["OPTIONS"] = {"charset": "utf8mb4"} # -# Debug +# Redis # -DEBUG = True +# The django-redis cache is used to establish concurrent locks using Redis. +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": parse_redis_connection(redis_database=0), + "TIMEOUT": 300, + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, + } +} -# Django Debug Toolbar -DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda _request: DEBUG and not TESTING} +# Redis Cacheops +CACHEOPS_REDIS = parse_redis_connection(redis_database=1) -if DEBUG and "debug_toolbar" not in INSTALLED_APPS: # noqa: F405 - INSTALLED_APPS.append("debug_toolbar") # noqa: F405 -if DEBUG and "debug_toolbar.middleware.DebugToolbarMiddleware" not in MIDDLEWARE: # noqa: F405 - MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") # noqa: F405 +# +# Celery settings are not defined here because they can be overloaded with +# environment variables. By default they use `CACHES["default"]["LOCATION"]`. +# # # Logging @@ -64,10 +89,8 @@ LOG_LEVEL = "DEBUG" if DEBUG else "INFO" -TESTING = len(sys.argv) > 1 and sys.argv[1] == "test" - # Verbose logging during normal development operation, but quiet logging during unit test execution -if not TESTING: +if not _TESTING: LOGGING = { "version": 1, "disable_existing_loggers": False, @@ -101,41 +124,11 @@ }, }, } -else: - LOGGING = {} # -# Redis +# Apps # -# 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": parse_redis_connection(redis_database=0), - "TIMEOUT": 300, - "OPTIONS": { - "CLIENT_CLASS": "django_redis.client.DefaultClient", - }, - } -} - -# RQ_QUEUES is not set here because it just uses the default that gets imported -# up top via `from nautobot.core.settings import *`. - -# Redis Cacheops -CACHEOPS_REDIS = parse_redis_connection(redis_database=1) - -# -# Celery settings are not defined here because they can be overloaded with -# environment variables. By default they use `CACHES["default"]["LOCATION"]`. -# - - # Enable installed plugins. Add the name of each plugin to the list. PLUGINS = ["nautobot_plugin_nornir", "nautobot_golden_config"] @@ -196,6 +189,7 @@ }, } +# TODO:Verify this is still needed # Modify django_jinja Environment for test cases django_jinja_config = None for template in TEMPLATES: diff --git a/invoke.example.yml b/invoke.example.yml index 60d1ca37..16db4580 100644 --- a/invoke.example.yml +++ b/invoke.example.yml @@ -3,7 +3,7 @@ nautobot_golden_config: project_name: "nautobot-golden-config" nautobot_ver: "latest" local: false - python_ver: "3.11" + python_ver: "3.8" compose_dir: "development" compose_files: - "docker-compose.base.yml" diff --git a/invoke.mysql.yml b/invoke.mysql.yml index 10f73b14..5ee06af6 100644 --- a/invoke.mysql.yml +++ b/invoke.mysql.yml @@ -3,7 +3,7 @@ nautobot_golden_config: project_name: "nautobot-golden-config" nautobot_ver: "latest" local: false - python_ver: "3.11" + python_ver: "3.8" compose_dir: "development" compose_files: - "docker-compose.base.yml" diff --git a/nautobot_golden_config/__init__.py b/nautobot_golden_config/__init__.py index a652bce3..d16e5bdd 100644 --- a/nautobot_golden_config/__init__.py +++ b/nautobot_golden_config/__init__.py @@ -8,10 +8,10 @@ from jinja2 import StrictUndefined from django.db.models.signals import post_migrate from nautobot.core.signals import nautobot_database_ready -from nautobot.extras.plugins import PluginConfig +from nautobot.extras.plugins import NautobotAppConfig -class GoldenConfig(PluginConfig): +class GoldenConfig(NautobotAppConfig): """Plugin configuration for the nautobot_golden_config plugin.""" name = "nautobot_golden_config" diff --git a/nautobot_golden_config/jobs.py b/nautobot_golden_config/jobs.py index 09afa3c3..f17a6811 100644 --- a/nautobot_golden_config/jobs.py +++ b/nautobot_golden_config/jobs.py @@ -1,5 +1,7 @@ """Jobs to run backups, intended config, and compliance.""" # pylint: disable=too-many-function-args,logging-fstring-interpolation +# TODO: Remove the following ignore, added to be able to pass pylint in CI. +# pylint: disable=arguments-differ from datetime import datetime @@ -163,9 +165,7 @@ def run(self, **data): # Instantiate a GitRepo object for each GitRepository in GoldenConfigSettings. intended_repos = get_refreshed_repos(job_obj=self, repo_type="intended_repository", data=data) - self.logger.debug( - "Building device settings mapping and running intended config nornir play." - ) + self.logger.debug("Building device settings mapping and running intended config nornir play.") config_intended(self, data) # Commit / Push each repo after job is completed. @@ -396,7 +396,7 @@ def run(self, **data): except NornirNautobotException as error: error_msg = str(error) self.logger.error(error_msg) - raise NornirNautobotException(error_msg) + raise NornirNautobotException(error_msg) from error if self._plan_type in ["intended", "missing", "remediation"]: self.logger.debug("Starting config plan generation for compliance features.") self._generate_config_plan_from_feature() @@ -439,7 +439,8 @@ def receive_job_button(self, obj): """Run config plan deployment process.""" self.logger.debug("Starting config plan deployment job.") data = {"debug": False, "config_plan": ConfigPlan.objects.filter(id=obj.id)} - config_deployment(self, **data=True) + # pylint: disable-next=unexpected-keyword-arg + config_deployment(self, **data) # Conditionally allow jobs based on whether or not turned on. diff --git a/nautobot_golden_config/nornir_plays/config_compliance.py b/nautobot_golden_config/nornir_plays/config_compliance.py index 66fdb420..07db01a3 100644 --- a/nautobot_golden_config/nornir_plays/config_compliance.py +++ b/nautobot_golden_config/nornir_plays/config_compliance.py @@ -143,7 +143,8 @@ def run_compliance( # pylint: disable=too-many-arguments,too-many-locals platform = obj.platform.network_driver if not rules.get(platform): error_msg = ( - f"E3007: There is no defined `Configuration Rule` for platform network_driver `{platform}`, preemptively failed." + f"E3007: There is no defined `Configuration Rule` for platform network_driver `{platform}`, " + "preemptively failed." ) logger.log_error(error_msg, extra={"object": obj}) raise NornirNautobotException(error_msg) diff --git a/nautobot_golden_config/tests/test_basic.py b/nautobot_golden_config/tests/test_basic.py index e25dc149..4bb988c9 100644 --- a/nautobot_golden_config/tests/test_basic.py +++ b/nautobot_golden_config/tests/test_basic.py @@ -1,9 +1,20 @@ """Basic tests that do not require Django.""" -import os import unittest - +import os import toml +from nautobot_golden_config import __version__ as project_version + + +class TestVersion(unittest.TestCase): + """Test Version is the same.""" + + def test_version(self): + """Verify that pyproject.toml version is same as version specified in the package.""" + parent_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) + poetry_version = toml.load(os.path.join(parent_path, "pyproject.toml"))["tool"]["poetry"]["version"] + self.assertEqual(project_version, poetry_version) + class TestDocsPackaging(unittest.TestCase): """Test Version in doc requirements is the same pyproject.""" diff --git a/poetry.lock b/poetry.lock index e6daf3a6..fb9944fb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "amqp" @@ -46,6 +46,17 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"] test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.22)"] +[[package]] +name = "appnope" +version = "0.1.3" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = "*" +files = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] + [[package]] name = "asgiref" version = "3.7.2" @@ -82,6 +93,23 @@ wrapt = [ {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, ] +[[package]] +name = "asttokens" +version = "2.4.0" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.0-py2.py3-none-any.whl", hash = "sha256:cf8fc9e61a86461aa9fb161a14a0841a03c405fa829ac6b202670b3495d2ce69"}, + {file = "asttokens-2.4.0.tar.gz", hash = "sha256:2e0171b991b2c959acc6c49318049236844a5da1d65ba2672c4880c1c894834e"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +test = ["astroid", "pytest"] + [[package]] name = "async-timeout" version = "4.0.3" @@ -125,6 +153,17 @@ files = [ [package.dependencies] pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +optional = false +python-versions = "*" +files = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] + [[package]] name = "backports-zoneinfo" version = "0.2.1" @@ -627,6 +666,70 @@ mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.2.0)", "types-Pill test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] test-no-images = ["pytest", "pytest-cov", "wurlitzer"] +[[package]] +name = "coverage" +version = "7.3.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136"}, + {file = "coverage-7.3.1-cp310-cp310-win32.whl", hash = "sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f"}, + {file = "coverage-7.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3"}, + {file = "coverage-7.3.1-cp311-cp311-win32.whl", hash = "sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a"}, + {file = "coverage-7.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f"}, + {file = "coverage-7.3.1-cp312-cp312-win32.whl", hash = "sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a"}, + {file = "coverage-7.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff"}, + {file = "coverage-7.3.1-cp38-cp38-win32.whl", hash = "sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3"}, + {file = "coverage-7.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8"}, + {file = "coverage-7.3.1-cp39-cp39-win32.whl", hash = "sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140"}, + {file = "coverage-7.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981"}, + {file = "coverage-7.3.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194"}, + {file = "coverage-7.3.1.tar.gz", hash = "sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952"}, +] + +[package.extras] +toml = ["tomli"] + [[package]] name = "cron-descriptor" version = "1.4.0" @@ -707,6 +810,17 @@ files = [ {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, ] +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + [[package]] name = "deepdiff" version = "6.4.1" @@ -1162,6 +1276,20 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "executing" +version = "1.2.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = "*" +files = [ + {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"}, + {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"}, +] + +[package.extras] +tests = ["asttokens", "littleutils", "pytest", "rich"] + [[package]] name = "flake8" version = "3.9.2" @@ -1535,6 +1663,45 @@ files = [ {file = "invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5"}, ] +[[package]] +name = "ipython" +version = "8.12.2" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ipython-8.12.2-py3-none-any.whl", hash = "sha256:ea8801f15dfe4ffb76dea1b09b847430ffd70d827b41735c64a0638a04103bfc"}, + {file = "ipython-8.12.2.tar.gz", hash = "sha256:c7b80eb7f5a855a88efc971fda506ff7a91c280b42cdae26643e0f601ea281ea"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" +typing-extensions = {version = "*", markers = "python_version < \"3.10\""} + +[package.extras] +all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] + [[package]] name = "isort" version = "5.12.0" @@ -1552,6 +1719,25 @@ pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib" plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] +[[package]] +name = "jedi" +version = "0.19.0" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.0-py2.py3-none-any.whl", hash = "sha256:cb8ce23fbccff0025e9386b5cf85e892f94c9b822378f8da49970471335ac64e"}, + {file = "jedi-0.19.0.tar.gz", hash = "sha256:bcf9894f1753969cbac8022a8c2eaee06bfa3724e4192470aaffe7eb6272b0c4"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + [[package]] name = "jinja2" version = "3.1.2" @@ -2015,16 +2201,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -2119,6 +2295,20 @@ pillow = ">=6.2.0" pyparsing = ">=2.3.1,<3.1" python-dateutil = ">=2.7" +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.5" +files = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] + +[package.dependencies] +traitlets = "*" + [[package]] name = "mccabe" version = "0.6.1" @@ -2706,6 +2896,21 @@ all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1 gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] invoke = ["invoke (>=2.0)"] +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + [[package]] name = "pathspec" version = "0.11.2" @@ -2728,6 +2933,31 @@ files = [ {file = "pbr-5.11.1.tar.gz", hash = "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"}, ] +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +optional = false +python-versions = "*" +files = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] + [[package]] name = "pillow" version = "10.0.0" @@ -2936,6 +3166,31 @@ files = [ {file = "psycopg2_binary-2.9.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb3b8d55924a6058a26db69fb1d3e7e32695ff8b491835ba9f479537e14dcf9f"}, ] +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] + +[package.extras] +tests = ["pytest"] + [[package]] name = "pycodestyle" version = "2.7.0" @@ -3254,6 +3509,20 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "python-dotenv" +version = "1.0.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, + {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "python3-openid" version = "3.2.0" @@ -3347,7 +3616,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -3355,15 +3623,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -3380,7 +3641,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -3388,7 +3648,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -3949,6 +4208,25 @@ dev = ["build", "flake8"] doc = ["sphinx"] test = ["pytest", "pytest-cov"] +[[package]] +name = "stack-data" +version = "0.6.2" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.2-py3-none-any.whl", hash = "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"}, + {file = "stack_data-0.6.2.tar.gz", hash = "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + [[package]] name = "stevedore" version = "5.1.0" @@ -4047,6 +4325,21 @@ files = [ {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, ] +[[package]] +name = "traitlets" +version = "5.9.0" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.7" +files = [ + {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, + {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] + [[package]] name = "transitions" version = "0.9.0" @@ -4339,5 +4632,5 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" -python-versions = "^3.8,<3.12" -content-hash = "78f0fbd14f3ec37f0d450c6506629a900ec58816ec3b219a938f54c80db1059f" +python-versions = ">=3.8,<3.12" +content-hash = "e4fcff9be10ff04cb47ad77598e2ed2525993cc94f232b0dd98aed79a420d418" diff --git a/pyproject.toml b/pyproject.toml index f9155520..3ca67bd0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,19 +2,13 @@ name = "nautobot-golden-config" version = "1.5.0" description = "A plugin for configuration on nautobot" -authors = ["Network to Code, LLC", ""] - +authors = ["Network to Code, LLC "] license = "Apache-2.0" - readme = "README.md" homepage = "https://docs.nautobot.com/projects/golden-config/en/latest/" repository = "https://github.com/nautobot/nautobot-plugin-golden-config" documentation = "https://docs.nautobot.com/projects/golden-config/en/latest/" keywords = ["nautobot", "nautobot-plugin"] -include = [ - "LICENSE", - "README.md", -] classifiers = [ "Intended Audience :: Developers", "Development Status :: 5 - Production/Stable", @@ -24,17 +18,20 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", ] +include = [ + "LICENSE", + "README.md", +] packages = [ { include = "nautobot_golden_config" }, ] - [tool.poetry.urls] "Changelog" = "https://docs.nautobot.com/projects/golden-config/en/latest/admin/release_notes/" "Bug Tracker" = "https://github.com/nautobot/nautobot-plugin-golden-config/issues" [tool.poetry.dependencies] -python = "^3.8,<3.12" +python = ">=3.8,<3.12" deepdiff = ">=5.5.0,!=6.0,!=6.1,<7" django-pivot = "^1.8.1" matplotlib = "^3.3.2" @@ -49,18 +46,19 @@ hier-config = "^2.2.2" [tool.poetry.group.dev.dependencies] bandit = "*" -# Black 23.x.x configuration changes and migration files are taken into account black = "*" +coverage = "*" django-debug-toolbar = "*" -# we need to pin flake8 because of package dependencies that cause it to downgrade and -# therefore cause issues with linting since older versions do not take .flake8 as config -flake8 = "^3.9.2" +flake8 = "*" invoke = "*" +ipython = "*" pydocstyle = "*" pylint = "*" pylint-django = "*" pylint-nautobot = { git = "https://github.com/nautobot/pylint-nautobot.git", branch = "develop" } # TODO: 2.0: Point to pypi +python-dotenv = "^1.0.0" yamllint = "*" +toml = "*" Markdown = "*" # Rendering docs to HTML mkdocs = "1.5.2" @@ -72,7 +70,6 @@ mkdocs-version-annotations = "1.0.0" mkdocstrings = "0.22.0" mkdocstrings-python = "1.5.2" - [tool.black] line-length = 120 target-version = ['py38', 'py39', 'py310', 'py311'] @@ -98,8 +95,11 @@ exclude = ''' [tool.pylint.master] # Include the pylint_django plugin to avoid spurious warnings about Django patterns -load-plugins="pylint_django,pylint_nautobot" -ignore=["jinja_filters.py", ".venv"] +load-plugins="pylint_django, pylint_nautobot" +ignore = [ + "jinja_filters.py", + ".venv", +] [tool.pylint.basic] # No docstrings required for private methods (Pylint default), or for test_ functions, or for inner Meta classes. @@ -131,6 +131,7 @@ match-dir = "(?!tests|migrations|development)[^\\.].*" # My docstring is on the line after the opening quotes instead of on the same line as them. # """ # We've discussed and concluded that we consider this to be a valid style choice. +# TODO D417 is not in the template, verify this add_ignore = "D212,D417" [build-system] @@ -144,4 +145,4 @@ testpaths = [ addopts = "-vv --doctest-modules" [tool.pylint-nautobot] -supported_nautobot_versions = ["2"] \ No newline at end of file +supported_nautobot_versions = ["2"] diff --git a/tasks.py b/tasks.py index f2e22f03..ce99d5a0 100644 --- a/tasks.py +++ b/tasks.py @@ -1,6 +1,6 @@ """Tasks for use with Invoke. -(c) 2020-2023 Network To Code +Copyright (c) 2023, Network to Code, LLC 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 @@ -12,11 +12,16 @@ limitations under the License. """ -import os from distutils.util import strtobool +from invoke import Collection, task as invoke_task +import os + +from dotenv import load_dotenv -from invoke import Collection -from invoke import task as invoke_task + +def _load_dotenv(): + load_dotenv("./development/development.env") + load_dotenv("./development/creds.env") def is_truthy(arg): @@ -41,7 +46,7 @@ def is_truthy(arg): { "nautobot_golden_config": { "nautobot_ver": "2.0.0-rc.2", - "project_name": "nautobot_golden_config", + "project_name": "nautobot-golden-config", "python_ver": "3.11", "local": False, "compose_dir": os.path.join(os.path.dirname(__file__), "development"), @@ -77,22 +82,23 @@ def task_wrapper(function=None): def docker_compose(context, command, **kwargs): - """Helper function for running a specific docker-compose command with all appropriate parameters and environment. + """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. + 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 = { - # Note: 'docker-compose logs' will stop following after 60 seconds by default, + # Note: 'docker compose logs' will stop following after 60 seconds by default, # so we are overriding that by setting this environment variable. "COMPOSE_HTTP_TIMEOUT": context.nautobot_golden_config.compose_http_timeout, "NAUTOBOT_VER": context.nautobot_golden_config.nautobot_ver, "PYTHON_VER": context.nautobot_golden_config.python_ver, + **kwargs.pop("env", {}), } compose_command_tokens = [ - "docker-compose", + "docker compose", f"--project-name {context.nautobot_golden_config.project_name}", f'--project-directory "{context.nautobot_golden_config.compose_dir}"', ] @@ -108,7 +114,7 @@ def docker_compose(context, command, **kwargs): if service is not None: compose_command_tokens.append(service) - print(f'Running docker-compose command "{command}"') + print(f'Running docker compose command "{command}"') compose_command = " ".join(compose_command_tokens) return context.run(compose_command, env=build_env, **kwargs) @@ -125,9 +131,11 @@ def run_command(context, command, **kwargs): if "nautobot" in results.stdout: compose_command = f"exec nautobot {command}" else: - compose_command = f"run --entrypoint '{command}' nautobot" + compose_command = f"run --rm --entrypoint '{command}' nautobot" + + pty = kwargs.pop("pty", True) - docker_compose(context, compose_command, pty=True) + docker_compose(context, compose_command, pty=pty, **kwargs) # ------------------------------------------------------------------------------ @@ -159,42 +167,66 @@ def generate_packages(context): run_command(context, command) +@task +def lock(context): + """Generate poetry.lock inside the Nautobot container.""" + run_command(context, "poetry lock --no-update") + + # ------------------------------------------------------------------------------ # START / STOP / DEBUG # ------------------------------------------------------------------------------ -@task -def debug(context): - """Start Nautobot and its dependencies in debug mode.""" - print("Starting Nautobot in debug mode...") - docker_compose(context, "up") +@task(help={"service": "If specified, only affect this service."}) +def debug(context, service=""): + """Start specified or all services and its dependencies in debug mode.""" + print(f"Starting {service} in debug mode...") + docker_compose(context, "up", service=service) @task(help={"service": "If specified, only affect this service."}) -def start(context, service=None): - """Start Nautobot and its dependencies in detached mode.""" +def start(context, service=""): + """Start specified or all services and its dependencies in detached mode.""" print("Starting Nautobot in detached mode...") docker_compose(context, "up --detach", service=service) -@task -def restart(context): - """Gracefully restart all containers.""" +@task(help={"service": "If specified, only affect this service."}) +def restart(context, service=""): + """Gracefully restart specified or all services.""" print("Restarting Nautobot...") - docker_compose(context, "restart") + docker_compose(context, "restart", service=service) -@task -def stop(context): - """Stop Nautobot and its dependencies.""" +@task(help={"service": "If specified, only affect this service."}) +def stop(context, service=""): + """Stop specified or all services, if service is not specified, remove all containers.""" print("Stopping Nautobot...") - docker_compose(context, "down") + docker_compose(context, "stop" if service else "down --remove-orphans", service=service) @task def destroy(context): """Destroy all containers and volumes.""" print("Destroying Nautobot...") - docker_compose(context, "down --volumes") + docker_compose(context, "down --remove-orphans --volumes") + + +@task +def export(context): + """Export docker compose configuration to `compose.yaml` file. + + Useful to: + + - Debug docker compose configuration. + - Allow using `docker compose` command directly without invoke. + """ + docker_compose(context, "convert > compose.yaml") + + +@task(name="ps", help={"all": "Show all, including stopped containers"}) +def ps_task(context, all=False): + """List containers.""" + docker_compose(context, f"ps {'--all' if all else ''}") @task @@ -207,13 +239,13 @@ def vscode(context): @task( help={ - "service": "Docker-compose service name to view (default: nautobot)", - "follow": "Follow logs", - "tail": "Tail N number of lines or 'all'", + "service": "If specified, only display logs for this service (default: all)", + "follow": "Flag to follow logs (default: False)", + "tail": "Tail N number of lines (default: all)", } ) -def logs(context, service="nautobot", follow=False, tail=None): - """View the logs of a docker-compose service.""" +def logs(context, service="", follow=False, tail=0): + """View the logs of a docker compose service.""" command = "logs " if follow: @@ -221,18 +253,21 @@ def logs(context, service="nautobot", follow=False, tail=None): if tail: command += f"--tail={tail} " - command += service - docker_compose(context, command) + docker_compose(context, command, service=service) # ------------------------------------------------------------------------------ # ACTIONS # ------------------------------------------------------------------------------ -@task -def nbshell(context): +@task(help={"file": "Python file to execute"}) +def nbshell(context, file=""): """Launch an interactive nbshell session.""" - command = "nautobot-server nbshell" - run_command(context, command) + command = [ + "nautobot-server", + "nbshell", + f"< '{file}'" if file else "", + ] + run_command(context, " ".join(command), pty=not bool(file)) @task @@ -244,7 +279,7 @@ def shell_plus(context): @task def cli(context): - """Launch a bash shell inside the running Nautobot container.""" + """Launch a bash shell inside the Nautobot container.""" run_command(context, "bash") @@ -302,6 +337,181 @@ def post_upgrade(context): run_command(context, command) +@task( + help={ + "service": "Docker compose service name to run command in (default: nautobot).", + "command": "Command to run (default: bash).", + "file": "File to run command with (default: empty)", + }, +) +def exec(context, service="nautobot", command="bash", file=""): + """Launch a command inside the running container (defaults to bash shell inside nautobot container).""" + command = [ + "exec", + "--", + service, + command, + f"< '{file}'" if file else "", + ] + docker_compose(context, " ".join(command), pty=not bool(file)) + + +@task( + help={ + "query": "SQL command to execute and quit (default: empty)", + "input": "SQL file to execute and quit (default: empty)", + "output": "Ouput file, overwrite if exists (default: empty)", + } +) +def dbshell(context, query="", input="", output=""): + """Start database CLI inside the running `db` container. + + Doesn't use `nautobot-server dbshell`, using started `db` service container only. + """ + if input and query: + raise ValueError("Cannot specify both, `input` and `query` arguments") + if output and not (input or query): + raise ValueError("`output` argument requires `input` or `query` argument") + + _load_dotenv() + + service = "db" + env_vars = {} + command = ["exec"] + + if "docker-compose.mysql.yml" in context.nautobot_golden_config.compose_files: + env_vars["MYSQL_PWD"] = os.getenv("MYSQL_PASSWORD") + command += [ + "--env=MYSQL_PWD", + "--", + service, + "mysql", + f"--user='{os.getenv('MYSQL_USER')}'", + f"--database='{os.getenv('MYSQL_DATABASE')}'", + ] + if query: + command += [f"--execute='{query}'"] + elif "docker-compose.postgres.yml" in context.nautobot_golden_config.compose_files: + command += [ + "--", + service, + "psql", + f"--username='{os.getenv('POSTGRES_USER')}'", + f"--dbname='{os.getenv('POSTGRES_DB')}'", + ] + if query: + command += [f"--command='{query}'"] + else: + raise ValueError("Unsupported database backend.") + + if input: + command += [f"< '{input}'"] + if output: + command += [f"> '{output}'"] + + docker_compose(context, " ".join(command), env=env_vars, pty=not (input or output or query)) + + +@task( + help={ + "input": "SQL dump file to replace the existing database with. This can be generated using `invoke backup-db` (default: `dump.sql`).", + } +) +def import_db(context, input="dump.sql"): + """Stop Nautobot containers and replace the current database with the dump into the running `db` container.""" + docker_compose(context, "stop -- nautobot worker") + + _load_dotenv() + + service = "db" + env_vars = {} + command = ["exec"] + + if "docker-compose.mysql.yml" in context.nautobot_golden_config.compose_files: + env_vars["MYSQL_PWD"] = os.getenv("MYSQL_PASSWORD") + command += [ + "--env=MYSQL_PWD", + "--", + service, + "mysql", + f"--user='{os.getenv('MYSQL_USER')}'", + f"--database='{os.getenv('MYSQL_DATABASE')}'", + ] + elif "docker-compose.postgres.yml" in context.nautobot_golden_config.compose_files: + command += [ + "--", + service, + "psql", + f"--username='{os.getenv('POSTGRES_USER')}'", + "postgres", + ] + else: + raise ValueError("Unsupported database backend.") + + command += [f"< '{input}'"] + + docker_compose(context, " ".join(command), env=env_vars, pty=False) + + print("Database import complete, you can start Nautobot now: `invoke start`") + + +@task( + help={ + "output": "Ouput file, overwrite if exists (default: `dump.sql`)", + "readable": "Flag to dump database data in more readable format (default: `True`)", + } +) +def backup_db(context, output="dump.sql", readable=True): + """Dump database into `output` file from running `db` container.""" + _load_dotenv() + + service = "db" + env_vars = {} + command = ["exec"] + + if "docker-compose.mysql.yml" in context.nautobot_golden_config.compose_files: + env_vars["MYSQL_PWD"] = os.getenv("MYSQL_ROOT_PASSWORD") + command += [ + "--env=MYSQL_PWD", + "--", + service, + "mysqldump", + "--user=root", + "--add-drop-database", + "--skip-extended-insert" if readable else "", + "--databases", + os.getenv("MYSQL_DATABASE", ""), + ] + elif "docker-compose.postgres.yml" in context.nautobot_golden_config.compose_files: + command += [ + "--", + service, + "pg_dump", + "--clean", + "--create", + "--if-exists", + f"--username='{os.getenv('POSTGRES_USER')}'", + f"--dbname='{os.getenv('POSTGRES_DB')}'", + ] + + if readable: + command += ["--inserts"] + else: + raise ValueError("Unsupported database backend.") + + if output: + command += [f"> '{output}'"] + + docker_compose(context, " ".join(command), env=env_vars, pty=False) + + print(50 * "=") + print("The database backup has been successfully completed and saved to the file:") + print(output) + print("If you want to import this database backup, please execute the following command:") + print(f"invoke import-db --input '{output}'") + print(50 * "=") + + # ------------------------------------------------------------------------------ # DOCS # ------------------------------------------------------------------------------ @@ -317,6 +527,25 @@ def docs(context): start(context, service="docs") +@task +def build_and_check_docs(context): + """Build documentation to be available within Nautobot.""" + command = "mkdocs build --no-directory-urls --strict" + run_command(context, command) + + +@task(name="help") +def help_task(context): + """Print the help of available tasks.""" + import tasks # pylint: disable=all + + root = Collection.from_module(tasks) + for task_name in sorted(root.task_names): + print(50 * "-") + print(f"invoke {task_name} --help") + context.run(f"invoke {task_name} --help") + + # ------------------------------------------------------------------------------ # TESTS # ------------------------------------------------------------------------------ @@ -387,7 +616,7 @@ def yamllint(context): @task def check_migrations(context): """Check for missing migrations.""" - command = "nautobot-server --config=nautobot/core/tests/nautobot_config.py makemigrations --dry-run --check" + command = "nautobot-server makemigrations --dry-run --check" run_command(context, command) @@ -398,9 +627,19 @@ def check_migrations(context): "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", + "pattern": "Run specific test methods, classes, or modules instead of all tests", + "verbose": "Enable verbose test output.", } ) -def unittest(context, keepdb=False, label="nautobot_golden_config", failfast=False, buffer=True): +def unittest( + context, + keepdb=False, + label="nautobot_golden_config", + failfast=False, + buffer=True, + pattern="", + verbose=False, +): """Run Nautobot unit tests.""" command = f"coverage run --module nautobot.core.cli test {label}" @@ -410,6 +649,11 @@ def unittest(context, keepdb=False, label="nautobot_golden_config", failfast=Fal command += " --failfast" if buffer: command += " --buffer" + if pattern: + command += f" -k='{pattern}'" + if verbose: + command += " --verbosity 2" + run_command(context, command) @@ -423,10 +667,12 @@ def unittest_coverage(context): @task( help={ - "failfast": "fail as soon as a single test fails don't run the entire test suite", + "failfast": "fail as soon as a single test fails don't run the entire test suite. (default: False)", + "keepdb": "Save and re-use test database between test runs for faster re-testing. (default: False)", + "lint-only": "Only run linters; unit tests will be excluded. (default: False)", } ) -def tests(context, failfast=False): +def tests(context, failfast=False, keepdb=False, lint_only=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_golden_config.local): @@ -445,7 +691,10 @@ def tests(context, failfast=False): yamllint(context) print("Running pylint...") pylint(context) - print("Running unit tests...") - unittest(context, failfast=failfast) + print("Running mkdocs...") + build_and_check_docs(context) + if not lint_only: + print("Running unit tests...") + unittest(context, failfast=failfast, keepdb=keepdb) print("All tests have passed!") unittest_coverage(context)