From 5c3b7a3bd5381210ce98b2f3f1f6796846fc179f Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Wed, 6 Nov 2024 16:11:36 -0600 Subject: [PATCH] Manual run of drift-manager to update the ltm-1.6 branch. (#268) * Update CI and other files for newest ltm cookie. --- .cookiecutter.json | 23 + .github/ISSUE_TEMPLATE/bug_report.md | 18 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 34 ++ .github/workflows/ci.yml | 137 ++++-- .gitignore | 6 +- development/Dockerfile | 25 +- development/docker-compose.base.yml | 21 +- development/docker-compose.dev.yml | 40 +- development/docker-compose.mysql.yml | 11 +- development/docker-compose.postgres.yml | 6 +- development/docker-compose.redis.yml | 1 - development/nautobot_config.py | 86 ++-- docs/requirements.txt | 2 +- mkdocs.yml | 13 +- poetry.lock | 550 +++++++++++----------- pyproject.toml | 11 +- tasks.py | 457 +++++++++++++++--- 18 files changed, 959 insertions(+), 484 deletions(-) create mode 100644 .cookiecutter.json create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.cookiecutter.json b/.cookiecutter.json new file mode 100644 index 00000000..ba921d4f --- /dev/null +++ b/.cookiecutter.json @@ -0,0 +1,23 @@ +{ + "cookiecutter": { + "codeowner_github_usernames": "@mzbroch @scetron @glennmatthews @chadell", + "full_name": "Network to Code, LLC", + "email": "info@networktocode.com", + "github_org": "nautobot", + "plugin_name": "nautobot_device_onboarding", + "verbose_name": "Device Onboarding", + "plugin_slug": "nautobot-device-onboarding", + "project_slug": "nautobot-app-device-onboarding", + "repo_url": "https://github.com/nautobot/nautobot-app-device-onboarding", + "base_url": "nautobot-device-onboarding", + "min_nautobot_version": "1.6.0", + "max_nautobot_version": "1.9999", + "camel_name": "NautobotDeviceOnboarding", + "project_short_description": "Device Onboarding", + "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/device-onboarding/en/latest", + "_template": "nautobot-plugin" + } +} \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f3bfd631..bea35341 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,10 +4,17 @@ about: Report a reproducible bug in the current release of nautobot-device-onboa --- ### Environment -* Python version: -* Nautobot version: +* Python version: +* Nautobot version: * nautobot-device-onboarding version: + +### Expected Behavior + + + +### Observed Behavior + -### Expected Behavior - - - -### Observed Behavior \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index ba609ddd..2cabafeb 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -5,7 +5,7 @@ about: Propose a new feature or enhancement --- ### Environment -* Nautobot version: +* Nautobot version: * nautobot-device-onboarding version: + +# Closes: # + +## What's Changed + + + +## To Do + + +- [ ] Explanation of Change(s) +- [ ] Attached Screenshots, Payload Example +- [ ] Unit, Integration Tests +- [ ] Documentation Updates (when adding/changing features) +- [ ] Example Plugin Updates (when adding/changing features) +- [ ] Outline Remaining Work, Constraints from Design diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8ae282f..83cbc79f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,65 +8,77 @@ on: # yamllint disable-line rule:truthy rule:comments branches: - "main" - "develop" + - "ltm-1.6" tags: - "v*" pull_request: ~ env: - PLUGIN_NAME: "nautobot-plugin-device-onboarding" + PLUGIN_NAME: "nautobot-device-onboarding" jobs: black: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_DEVICE_ONBOARDING_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: black" run: "poetry run invoke black" bandit: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_DEVICE_ONBOARDING_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: bandit" run: "poetry run invoke bandit" pydocstyle: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_DEVICE_ONBOARDING_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: pydocstyle" run: "poetry run invoke pydocstyle" flake8: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_DEVICE_ONBOARDING_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: flake8" run: "poetry run invoke flake8" + poetry: + runs-on: "ubuntu-22.04" + env: + INVOKE_NAUTOBOT_DEVICE_ONBOARDING_LOCAL: "True" + steps: + - name: "Check out repository code" + uses: "actions/checkout@v4" + - name: "Setup environment" + uses: "networktocode/gh-action-setup-poetry-environment@v4" + - name: "Checking: poetry lock file" + run: "poetry run invoke lock --check" yamllint: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_DEVICE_ONBOARDING_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: yamllint" @@ -76,27 +88,28 @@ jobs: - "bandit" - "pydocstyle" - "flake8" + - "poetry" - "yamllint" - "black" - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" strategy: fail-fast: true matrix: - python-version: ["3.8"] - nautobot-version: ["1.4.10"] + python-version: ["3.11"] + nautobot-version: ["1.6.0"] env: INVOKE_NAUTOBOT_DEVICE_ONBOARDING_PYTHON_VER: "${{ matrix.python-version }}" INVOKE_NAUTOBOT_DEVICE_ONBOARDING_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Set up Docker Buildx" id: "buildx" - uses: "docker/setup-buildx-action@v1" + uses: "docker/setup-buildx-action@v3" - name: "Build" - uses: "docker/build-push-action@v2" + uses: "docker/build-push-action@v5" with: builder: "${{ steps.buildx.outputs.name }}" context: "./" @@ -113,40 +126,80 @@ jobs: run: "cp development/creds.example.env development/creds.env" - name: "Linting: pylint" run: "poetry run invoke pylint" + check-migrations: + needs: + - "bandit" + - "pydocstyle" + - "flake8" + - "poetry" + - "yamllint" + - "black" + runs-on: "ubuntu-22.04" + strategy: + fail-fast: true + matrix: + python-version: ["3.11"] + nautobot-version: ["1.6"] + env: + INVOKE_NAUTOBOT_DEVICE_ONBOARDING_PYTHON_VER: "${{ matrix.python-version }}" + INVOKE_NAUTOBOT_DEVICE_ONBOARDING_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" + steps: + - name: "Check out repository code" + uses: "actions/checkout@v4" + - name: "Setup environment" + uses: "networktocode/gh-action-setup-poetry-environment@v4" + - name: "Set up Docker Buildx" + id: "buildx" + uses: "docker/setup-buildx-action@v3" + - name: "Build" + uses: "docker/build-push-action@v5" + with: + builder: "${{ steps.buildx.outputs.name }}" + context: "./" + push: false + load: true + tags: "${{ env.PLUGIN_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + file: "./development/Dockerfile" + cache-from: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + cache-to: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + build-args: | + NAUTOBOT_VER=${{ matrix.nautobot-version }} + PYTHON_VER=${{ matrix.python-version }} + - name: "Copy credentials" + run: "cp development/creds.example.env development/creds.env" + - name: "Checking: migrations" + run: "poetry run invoke check-migrations" unittest: needs: - "pylint" + - "check-migrations" strategy: fail-fast: true matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.11"] db-backend: ["postgresql"] - nautobot-version: ["stable"] + nautobot-version: ["1.6"] include: - - python-version: "3.10" + - python-version: "3.11" db-backend: "postgresql" - nautobot-version: "1.4.10" - - python-version: "3.8" + nautobot-version: "1.6.0" + - python-version: "3.11" db-backend: "mysql" - nautobot-version: "1.5.5" - - python-version: "3.10" - db-backend: "mysql" - nautobot-version: "latest" - - runs-on: "ubuntu-20.04" + nautobot-version: "1.6" + runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_DEVICE_ONBOARDING_PYTHON_VER: "${{ matrix.python-version }}" INVOKE_NAUTOBOT_DEVICE_ONBOARDING_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Set up Docker Buildx" id: "buildx" - uses: "docker/setup-buildx-action@v1" + uses: "docker/setup-buildx-action@v3" - name: "Build" - uses: "docker/build-push-action@v2" + uses: "docker/build-push-action@v5" with: builder: "${{ steps.buildx.outputs.name }}" context: "./" @@ -170,15 +223,15 @@ jobs: needs: - "unittest" name: "Publish to GitHub" - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" if: "startsWith(github.ref, 'refs/tags/v')" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Set up Python" - uses: "actions/setup-python@v2" + uses: "actions/setup-python@v4" with: - python-version: "3.9" + python-version: "3.11" - name: "Install Python Packages" run: "pip install poetry" - name: "Set env" @@ -190,7 +243,7 @@ jobs: - name: "Upload binaries to release" uses: "svenstaro/upload-release-action@v2" with: - repo_token: "${{ secrets.GH_NAUTOBOT_BOT_TOKEN }}" + repo_token: "${{ secrets.NTC_GITHUB_TOKEN }}" # use GH_NAUTOBOT_BOT_TOKEN for Nautobot Org repos. file: "dist/*" tag: "${{ github.ref }}" overwrite: true @@ -199,15 +252,15 @@ jobs: needs: - "unittest" name: "Push Package to PyPI" - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" if: "startsWith(github.ref, 'refs/tags/v')" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Set up Python" - uses: "actions/setup-python@v2" + uses: "actions/setup-python@v4" with: - python-version: "3.9" + python-version: "3.11" - name: "Install Python Packages" run: "pip install poetry" - name: "Set env" @@ -225,7 +278,7 @@ jobs: needs: - "publish_gh" - "publish_pypi" - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: SLACK_WEBHOOK_URL: "${{ secrets.SLACK_WEBHOOK_URL }}" SLACK_MESSAGE: >- @@ -238,7 +291,7 @@ jobs: # ENVs cannot be used directly in job.if. This is a workaround to check # if SLACK_WEBHOOK_URL is present. if: "env.SLACK_WEBHOOK_URL != ''" - uses: "slackapi/slack-github-action@v1.17.0" + uses: "slackapi/slack-github-action@v1" with: payload: | { diff --git a/.gitignore b/.gitignore index b63d7ed1..99bbe8a9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ # Swap files *.swp -dist # Byte-compiled / optimized / DLL files __pycache__/ @@ -304,6 +303,7 @@ development/*.txt invoke.yml # Docs -docs/README.md -docs/CHANGELOG.md public +/compose.yaml +/dump.sql +/nautobot_device_onboarding/static/nautobot_device_onboarding/docs diff --git a/development/Dockerfile b/development/Dockerfile index 5911fe51..1363ad91 100644 --- a/development/Dockerfile +++ b/development/Dockerfile @@ -6,11 +6,11 @@ # ------------------------------------------------------------------------------------- # !!! USE CAUTION WHEN MODIFYING LINES BELOW -# Accepts a desired Nautobot version as build argument, default to 1.4.10 -ARG NAUTOBOT_VER="1.4.10" +# Accepts a desired Nautobot version as build argument, default to 1.6.0 +ARG NAUTOBOT_VER="1.6.0" -# Accepts a desired Python version as build argument, default to 3.8 -ARG PYTHON_VER="3.8" +# Accepts a desired Python version as build argument, default to 3.11 +ARG PYTHON_VER="3.11" # Retrieve published development image of Nautobot base which should include most CI dependencies FROM ghcr.io/nautobot/nautobot-dev:${NAUTOBOT_VER}-py${PYTHON_VER} @@ -19,20 +19,21 @@ FROM ghcr.io/nautobot/nautobot-dev:${NAUTOBOT_VER}-py${PYTHON_VER} ARG NAUTOBOT_ROOT=/opt/nautobot ENV prometheus_multiproc_dir=/prom_cache -ENV NAUTOBOT_ROOT ${NAUTOBOT_ROOT} +ENV NAUTOBOT_ROOT=${NAUTOBOT_ROOT} +ENV INVOKE_NAUTOBOT_DEVICE_ONBOARDING_LOCAL=true # Install Poetry manually via its installer script; # We might be using an older version of Nautobot that includes an older version of Poetry # and CI and local development may have a newer version of Poetry # Since this is only used for development and we don't ship this container, pinning Poetry back is not expressly necessary # We also don't need virtual environments in container -RUN curl -sSL https://install.python-poetry.org | python3 - && \ +RUN which poetry || curl -sSL https://install.python-poetry.org | python3 - && \ poetry config virtualenvs.create false # !!! USE CAUTION WHEN MODIFYING LINES ABOVE # ------------------------------------------------------------------------------------- # App-specifc system build/test dependencies. -# +# # Example: LDAP requires `libldap2-dev` to be apt-installed before the Python package. # ------------------------------------------------------------------------------------- # --> Start safe to modify section @@ -62,17 +63,19 @@ RUN pip show nautobot | grep "^Version: " | sed -e 's/Version: /nautobot==/' > c # # We can't use the entire freeze as it takes forever to resolve with rigidly fixed non-direct dependencies, # especially those that are only direct to Nautobot but the container included versions slightly mismatch -RUN poetry export -f requirements.txt --without-hashes --output poetry_freeze_base.txt -RUN poetry export -f requirements.txt --with dev --without-hashes --output poetry_freeze_all.txt +RUN poetry export -f requirements.txt --without-hashes --extras all --output poetry_freeze_base.txt +RUN poetry export -f requirements.txt --without-hashes --extras all --with dev --output poetry_freeze_all.txt 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 app -RUN pip install -c constraints.txt -e . +RUN --mount=type=cache,target="/root/.cache/pip",sharing=locked \ + pip install -c constraints.txt -e .[all] # Install any dev dependencies frozen from Poetry # Can be improved in Poetry 1.2 which allows `poetry install --only dev` -RUN pip install -c constraints.txt -r poetry_freeze_dev.txt +RUN --mount=type=cache,target="/root/.cache/pip",sharing=locked \ + pip install -c constraints.txt -r poetry_freeze_dev.txt COPY development/nautobot_config.py ${NAUTOBOT_ROOT}/nautobot_config.py # !!! USE CAUTION WHEN MODIFYING LINES ABOVE diff --git a/development/docker-compose.base.yml b/development/docker-compose.base.yml index 6dfd6ade..c5675d14 100644 --- a/development/docker-compose.base.yml +++ b/development/docker-compose.base.yml @@ -13,7 +13,6 @@ x-nautobot-base: &nautobot-base - "creds.env" tty: true -version: "3.8" services: nautobot: depends_on: @@ -21,15 +20,17 @@ services: condition: "service_started" db: condition: "service_healthy" - <<: *nautobot-build - <<: *nautobot-base + <<: + - *nautobot-base + - *nautobot-build 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 + - "nautobot-server celery worker -l $$NAUTOBOT_LOG_LEVEL --events" ## $$ because of docker-compose depends_on: - - "nautobot" + nautobot: + condition: "service_healthy" healthcheck: interval: "30s" timeout: "10s" @@ -37,3 +38,13 @@ services: retries: 3 test: ["CMD", "bash", "-c", "nautobot-server celery inspect ping --destination celery@$$HOSTNAME"] ## $$ because of docker-compose <<: *nautobot-base + beat: + entrypoint: + - "sh" + - "-c" # this is to evaluate the $NAUTOBOT_LOG_LEVEL from the env + - "nautobot-server celery beat -l $$NAUTOBOT_LOG_LEVEL" ## $$ because of docker-compose + depends_on: + - "nautobot" + healthcheck: + disable: true + <<: *nautobot-base diff --git a/development/docker-compose.dev.yml b/development/docker-compose.dev.yml index a0294b33..25ab24bf 100644 --- a/development/docker-compose.dev.yml +++ b/development/docker-compose.dev.yml @@ -3,7 +3,6 @@ # any override will need to include these volumes to use them. # see: https://github.com/docker/compose/issues/3729 --- -version: "3.8" services: nautobot: command: "nautobot-server runserver 0.0.0.0:8080" @@ -12,25 +11,46 @@ services: volumes: - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" - "../:/source" + healthcheck: + interval: "30s" + timeout: "10s" + start_period: "60s" + retries: 3 + 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: - "8001:8080" volumes: - - "../docs:/source/docs:ro" - - "../mkdocs.yml:/source/mkdocs.yml:ro" + - "../:/source" image: "nautobot-device-onboarding/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}" healthcheck: 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" - # To expose postgres or redis to the host uncomment the following - # db: - # ports: - # - "5432:5432" - # redis: - # ports: - # - "6379:6379" + healthcheck: + test: ["CMD", "true"] # Due to layering, disable: true won't work. Instead, change the test + beat: + entrypoint: + - "sh" + - "-c" # this is to evaluate the $NAUTOBOT_LOG_LEVEL from the env + - "watchmedo auto-restart --directory './' --pattern '*.py' --recursive -- nautobot-server celery beat -l $$NAUTOBOT_LOG_LEVEL" ## $$ because of docker-compose + volumes: + - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" + - "../:/source" + 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: +# - "5432:5432" +# redis: +# ports: +# - "6379:6379" diff --git a/development/docker-compose.mysql.yml b/development/docker-compose.mysql.yml index c7fa6a1f..dbe31cba 100644 --- a/development/docker-compose.mysql.yml +++ b/development/docker-compose.mysql.yml @@ -1,6 +1,4 @@ --- -version: "3.8" - services: nautobot: environment: @@ -19,7 +17,7 @@ services: db: image: "mysql:8" command: - - "--default-authentication-plugin=mysql_native_password" + - "--max_connections=1000" env_file: - "development.env" - "creds.env" @@ -27,7 +25,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..8d96fdba 100644 --- a/development/docker-compose.postgres.yml +++ b/development/docker-compose.postgres.yml @@ -1,17 +1,17 @@ --- -version: "3.8" - services: nautobot: environment: - "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/docker-compose.redis.yml b/development/docker-compose.redis.yml index 6da9fa01..b5e266a3 100644 --- a/development/docker-compose.redis.yml +++ b/development/docker-compose.redis.yml @@ -1,5 +1,4 @@ --- -version: "3.8" services: redis: image: "redis:6-alpine" diff --git a/development/nautobot_config.py b/development/nautobot_config.py index 91b0643a..dccfadd1 100644 --- a/development/nautobot_config.py +++ b/development/nautobot_config.py @@ -1,11 +1,24 @@ """Nautobot development configuration file.""" -# pylint: disable=invalid-envvar-default import os import sys -from nautobot.core.settings import * # noqa: F403 -from nautobot.core.settings_funcs import parse_redis_connection +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 @@ -14,6 +27,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 = { @@ -43,18 +59,25 @@ DATABASES["default"]["OPTIONS"] = {"charset": "utf8mb4"} # -# Debug +# Redis # -DEBUG = True - -# Django Debug Toolbar -DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda _request: DEBUG and not TESTING} +# 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", + }, + } +} -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 @@ -62,10 +85,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 +122,14 @@ } # -# Redis -# - -# 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"]`. +# Apps # -# Enable installed plugins. Add the name of each plugin to the list. +# Enable installed Apps. Add the name of each App to the list. PLUGINS = ["nautobot_device_onboarding"] -# 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. +# Apps configuration settings. These settings are used by various Apps that the user may have installed. +# Each key in the dictionary is the name of an installed App and its value is a dictionary of settings. # PLUGINS_CONFIG = { # 'nautobot_device_onboarding': { # 'foo': 'bar', diff --git a/docs/requirements.txt b/docs/requirements.txt index 40af0e0e..bf10c13b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,6 @@ mkdocs==1.6.0 mkdocs-material==9.5.32 -mkdocs-version-annotations==1.0.0 +markdown-version-annotations==1.0.1 griffe==1.1.1 mkdocstrings-python==1.10.8 mkdocstrings==0.25.2 diff --git a/mkdocs.yml b/mkdocs.yml index 2ccd2a88..1b72679e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,13 +14,17 @@ theme: - "django" - "yaml" features: - - "navigation.tracking" + - "content.action.edit" + - "content.action.view" + - "content.code.copy" + - "navigation.footer" + - "navigation.indexes" - "navigation.tabs" - "navigation.tabs.sticky" - - "search.suggest" + - "navigation.tracking" - "search.highlight" - "search.share" - - "navigation.indexes" + - "search.suggest" favicon: "assets/favicon.ico" logo: "assets/nautobot_logo.svg" palette: @@ -68,6 +72,8 @@ extra: link: "https://twitter.com/networktocode" name: "Network to Code Twitter" markdown_extensions: + - "markdown_version_annotations": + admonition_tag: "???" - "admonition" - "toc": permalink: true @@ -81,7 +87,6 @@ markdown_extensions: - "footnotes" plugins: - "search" - - "mkdocs-version-annotations" - "mkdocstrings": default_handler: "python" handlers: diff --git a/poetry.lock b/poetry.lock index 197c879b..c977f5ab 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "amqp" @@ -212,13 +212,13 @@ typecheck = ["mypy"] [[package]] name = "billiard" -version = "3.6.4.0" +version = "4.2.1" description = "Python multiprocessing fork with improvements and bugfixes" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "billiard-3.6.4.0-py3-none-any.whl", hash = "sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b"}, - {file = "billiard-3.6.4.0.tar.gz", hash = "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547"}, + {file = "billiard-4.2.1-py3-none-any.whl", hash = "sha256:40b59a4ac8806ba2c2369ea98d876bc6108b051c227baffd928c644d15d8f3cb"}, + {file = "billiard-4.2.1.tar.gz", hash = "sha256:12b641b0c539073fc8d3f5b8b7be998956665c4233c7c1fcd66a7e677c4fb36f"}, ] [[package]] @@ -269,57 +269,59 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "celery" -version = "5.2.7" +version = "5.3.6" description = "Distributed Task Queue." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "celery-5.2.7-py3-none-any.whl", hash = "sha256:138420c020cd58d6707e6257b6beda91fd39af7afde5d36c6334d175302c0e14"}, - {file = "celery-5.2.7.tar.gz", hash = "sha256:fafbd82934d30f8a004f81e8f7a062e31413a23d444be8ee3326553915958c6d"}, + {file = "celery-5.3.6-py3-none-any.whl", hash = "sha256:9da4ea0118d232ce97dff5ed4974587fb1c0ff5c10042eb15278487cdd27d1af"}, + {file = "celery-5.3.6.tar.gz", hash = "sha256:870cc71d737c0200c397290d730344cc991d13a057534353d124c9380267aab9"}, ] [package.dependencies] -billiard = ">=3.6.4.0,<4.0" -click = ">=8.0.3,<9.0" -click-didyoumean = ">=0.0.3" +"backports.zoneinfo" = {version = ">=0.2.1", markers = "python_version < \"3.9\""} +billiard = ">=4.2.0,<5.0" +click = ">=8.1.2,<9.0" +click-didyoumean = ">=0.3.0" click-plugins = ">=1.1.1" click-repl = ">=0.2.0" -kombu = ">=5.2.3,<6.0" -pytz = ">=2021.3" -vine = ">=5.0.0,<6.0" +kombu = ">=5.3.4,<6.0" +python-dateutil = ">=2.8.2" +tzdata = ">=2022.7" +vine = ">=5.1.0,<6.0" [package.extras] -arangodb = ["pyArango (>=1.3.2)"] -auth = ["cryptography"] -azureblockblob = ["azure-storage-blob (==12.9.0)"] +arangodb = ["pyArango (>=2.0.2)"] +auth = ["cryptography (==41.0.5)"] +azureblockblob = ["azure-storage-blob (>=12.15.0)"] brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"] -cassandra = ["cassandra-driver (<3.21.0)"] -consul = ["python-consul2"] -cosmosdbsql = ["pydocumentdb (==2.3.2)"] +cassandra = ["cassandra-driver (>=3.25.0,<4)"] +consul = ["python-consul2 (==0.1.5)"] +cosmosdbsql = ["pydocumentdb (==2.3.5)"] couchbase = ["couchbase (>=3.0.0)"] -couchdb = ["pycouchdb"] -django = ["Django (>=1.11)"] -dynamodb = ["boto3 (>=1.9.178)"] -elasticsearch = ["elasticsearch"] +couchdb = ["pycouchdb (==1.14.2)"] +django = ["Django (>=2.2.28)"] +dynamodb = ["boto3 (>=1.26.143)"] +elasticsearch = ["elastic-transport (<=8.10.0)", "elasticsearch (<=8.11.0)"] eventlet = ["eventlet (>=0.32.0)"] gevent = ["gevent (>=1.5.0)"] -librabbitmq = ["librabbitmq (>=1.5.0)"] -memcache = ["pylibmc"] -mongodb = ["pymongo[srv] (>=3.11.1)"] -msgpack = ["msgpack"] -pymemcache = ["python-memcached"] -pyro = ["pyro4"] -pytest = ["pytest-celery"] -redis = ["redis (>=3.4.1,!=4.0.0,!=4.0.1)"] -s3 = ["boto3 (>=1.9.125)"] +librabbitmq = ["librabbitmq (>=2.0.0)"] +memcache = ["pylibmc (==1.6.3)"] +mongodb = ["pymongo[srv] (>=4.0.2)"] +msgpack = ["msgpack (==1.0.7)"] +pymemcache = ["python-memcached (==1.59)"] +pyro = ["pyro4 (==4.82)"] +pytest = ["pytest-celery (==0.0.0)"] +redis = ["redis (>=4.5.2,!=4.5.5,<6.0.0)"] +s3 = ["boto3 (>=1.26.143)"] slmq = ["softlayer-messaging (>=1.0.3)"] -solar = ["ephem"] -sqlalchemy = ["sqlalchemy"] -sqs = ["kombu[sqs]"] +solar = ["ephem (==4.1.5)"] +sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"] +sqs = ["boto3 (>=1.26.143)", "kombu[sqs] (>=5.3.0)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"] tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"] yaml = ["PyYAML (>=3.10)"] zookeeper = ["kazoo (>=1.3.1)"] -zstd = ["zstandard"] +zstd = ["zstandard (==0.22.0)"] [[package]] name = "certifi" @@ -566,6 +568,20 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "cron-descriptor" +version = "1.4.5" +description = "A Python library that converts cron expressions into human readable strings." +optional = false +python-versions = "*" +files = [ + {file = "cron_descriptor-1.4.5-py3-none-any.whl", hash = "sha256:736b3ae9d1a99bc3dbfc5b55b5e6e7c12031e7ba5de716625772f8b02dcd6013"}, + {file = "cron_descriptor-1.4.5.tar.gz", hash = "sha256:f51ce4ffc1d1f2816939add8524f206c376a42c87a5fca3091ce26725b3b1bca"}, +] + +[package.extras] +dev = ["polib"] + [[package]] name = "cryptography" version = "41.0.3" @@ -683,13 +699,12 @@ django = "*" [[package]] name = "django-cacheops" -version = "6.0" +version = "6.2" description = "A slick ORM cache with automatic granular event-driven invalidation for Django." optional = false python-versions = ">=3.5" files = [ - {file = "django-cacheops-6.0.tar.gz", hash = "sha256:78e161ebd96a32e28e19ec7da31f2afed9e62a79726b8b5f0ed12dd16c2e5841"}, - {file = "django_cacheops-6.0-py2.py3-none-any.whl", hash = "sha256:ee38b969c9fc68f7c88e769b6c811e19563cca1ae08210d9f553ff758b6c3e17"}, + {file = "django-cacheops-6.2.tar.gz", hash = "sha256:cc73fd0a1c14799253ff20a8a45791a3c8d2802217b301e70cfa08ae819e438f"}, ] [package.dependencies] @@ -700,20 +715,23 @@ six = ">=1.4.0" [[package]] name = "django-celery-beat" -version = "2.2.1" +version = "2.5.0" description = "Database-backed Periodic Tasks." optional = false python-versions = "*" files = [ - {file = "django-celery-beat-2.2.1.tar.gz", hash = "sha256:97ae5eb309541551bdb07bf60cc57cadacf42a74287560ced2d2c06298620234"}, - {file = "django_celery_beat-2.2.1-py2.py3-none-any.whl", hash = "sha256:ab43049634fd18dc037927d7c2c7d5f67f95283a20ebbda55f42f8606412e66c"}, + {file = "django-celery-beat-2.5.0.tar.gz", hash = "sha256:cd0a47f5958402f51ac0c715bc942ae33d7b50b4e48cba91bc3f2712be505df1"}, + {file = "django_celery_beat-2.5.0-py3-none-any.whl", hash = "sha256:ae460faa5ea142fba0875409095d22f6bd7bcc7377889b85e8cab5c0dfb781fe"}, ] [package.dependencies] -celery = ">=5.0,<6.0" -Django = ">=2.2,<4.0" -django-timezone-field = ">=4.1.0,<5.0" +"backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} +celery = ">=5.2.3,<6.0" +cron-descriptor = ">=1.2.32" +Django = ">=2.2,<5.0" +django-timezone-field = ">=5.0" python-crontab = ">=2.3.4" +tzdata = "*" [[package]] name = "django-constance" @@ -735,13 +753,13 @@ redis = ["redis"] [[package]] name = "django-cors-headers" -version = "3.13.0" +version = "4.2.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "django-cors-headers-3.13.0.tar.gz", hash = "sha256:f9dc6b4e3f611c3199700b3e5f3398c28757dcd559c2f82932687f3d0443cfdf"}, - {file = "django_cors_headers-3.13.0-py3-none-any.whl", hash = "sha256:37e42883b5f1f2295df6b4bba96eb2417a14a03270cb24b2a07f021cd4487cf4"}, + {file = "django_cors_headers-4.2.0-py3-none-any.whl", hash = "sha256:9ada212b0e2efd4a5e339360ffc869cb21ac5605e810afe69f7308e577ea5bde"}, + {file = "django_cors_headers-4.2.0.tar.gz", hash = "sha256:f9749c6410fe738278bc2b6ef17f05195bc7b251693c035752d8257026af024f"}, ] [package.dependencies] @@ -749,17 +767,17 @@ Django = ">=3.2" [[package]] name = "django-cryptography" -version = "1.0" +version = "1.1" description = "Easily encrypt data in Django" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "django-cryptography-1.0.tar.gz", hash = "sha256:13de5cf8f1250744c104b9e24774d03aa6d8488959dd40cdc016934043652445"}, - {file = "django_cryptography-1.0-py3-none-any.whl", hash = "sha256:0a99980b1cee7cc5e52f9b20b322620fea7cc124d770273e7bd285b20fd9d222"}, + {file = "django_cryptography-1.1-py2.py3-none-any.whl", hash = "sha256:93702fcf0d75865d55362f20ecd95274c4eef60ccdce46cbdade0420acee07cb"}, ] [package.dependencies] cryptography = "*" +Django = "*" django-appconf = "*" [[package]] @@ -806,27 +824,27 @@ Django = ">=3.2" [[package]] name = "django-filter" -version = "21.1" +version = "23.1" description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "django-filter-21.1.tar.gz", hash = "sha256:632a251fa8f1aadb4b8cceff932bb52fe2f826dd7dfe7f3eac40e5c463d6836e"}, - {file = "django_filter-21.1-py3-none-any.whl", hash = "sha256:f4a6737a30104c98d2e2a5fb93043f36dd7978e0c7ddc92f5998e85433ea5063"}, + {file = "django-filter-23.1.tar.gz", hash = "sha256:dee5dcf2cea4d7f767e271b6d01f767fce7500676d5e5dc58dac8154000b87df"}, + {file = "django_filter-23.1-py3-none-any.whl", hash = "sha256:e3c52ad83c32fb5882125105efb5fea2a1d6a85e7dc64b04ef52edbf14451b6c"}, ] [package.dependencies] -Django = ">=2.2" +Django = ">=3.2" [[package]] name = "django-health-check" -version = "3.16.7" +version = "3.17.0" description = "Run checks on services like databases, queue servers, celery processes, etc." optional = false python-versions = ">=3.8" files = [ - {file = "django-health-check-3.16.7.tar.gz", hash = "sha256:85b8e4ffa6ebbee3a7214c91ea4a67ce0e918bc8ed9679d054afd9cc9fa17c4f"}, - {file = "django_health_check-3.16.7-py2.py3-none-any.whl", hash = "sha256:4f4fe32838eb367b9dda51669f128b97f8416eaa66b80b58c50db6fc2cc42356"}, + {file = "django-health-check-3.17.0.tar.gz", hash = "sha256:d1b8671e79d1de6e3dd1a9c69566222b0bfcfacca8b90511a4407b2d0d3d2778"}, + {file = "django_health_check-3.17.0-py2.py3-none-any.whl", hash = "sha256:20dc5ccb516a4e7163593fd4026f0a7531e3027b47d23ebe3bd9dbc99ac4354c"}, ] [package.dependencies] @@ -904,13 +922,13 @@ tests = ["tox"] [[package]] name = "django-prometheus" -version = "2.2.0" +version = "2.3.1" description = "Django middlewares to monitor your application with Prometheus.io." optional = false python-versions = "*" files = [ - {file = "django-prometheus-2.2.0.tar.gz", hash = "sha256:240378a1307c408bd5fc85614a3a57f1ce633d4a222c9e291e2bbf325173b801"}, - {file = "django_prometheus-2.2.0-py2.py3-none-any.whl", hash = "sha256:e6616770d8820b8834762764bf1b76ec08e1b98e72a6f359d488a2e15fe3537c"}, + {file = "django-prometheus-2.3.1.tar.gz", hash = "sha256:f9c8b6c780c9419ea01043c63a437d79db2c33353451347894408184ad9c3e1e"}, + {file = "django_prometheus-2.3.1-py2.py3-none-any.whl", hash = "sha256:cf9b26f7ba2e4568f08f8f91480a2882023f5908579681bcf06a4d2465f12168"}, ] [package.dependencies] @@ -918,17 +936,17 @@ prometheus-client = ">=0.7" [[package]] name = "django-redis" -version = "5.2.0" +version = "5.3.0" description = "Full featured redis cache backend for Django." optional = false python-versions = ">=3.6" files = [ - {file = "django-redis-5.2.0.tar.gz", hash = "sha256:8a99e5582c79f894168f5865c52bd921213253b7fd64d16733ae4591564465de"}, - {file = "django_redis-5.2.0-py3-none-any.whl", hash = "sha256:1d037dc02b11ad7aa11f655d26dac3fb1af32630f61ef4428860a2e29ff92026"}, + {file = "django-redis-5.3.0.tar.gz", hash = "sha256:8bc5793ec06b28ea802aad85ec437e7646511d4e571e07ccad19cfed8b9ddd44"}, + {file = "django_redis-5.3.0-py3-none-any.whl", hash = "sha256:2d8660d39f586c41c9907d5395693c477434141690fd7eca9d32376af00b0aac"}, ] [package.dependencies] -Django = ">=2.2" +Django = ">=3.2" redis = ">=3,<4.0.0 || >4.0.0,<4.0.1 || >4.0.1" [package.extras] @@ -936,19 +954,19 @@ hiredis = ["redis[hiredis] (>=3,!=4.0.0,!=4.0.1)"] [[package]] name = "django-rq" -version = "2.5.1" +version = "2.8.1" description = "An app that provides django integration for RQ (Redis Queue)" optional = false python-versions = "*" files = [ - {file = "django-rq-2.5.1.tar.gz", hash = "sha256:f08486602664d73a6e335872c868d79663e380247e6307496d01b8fa770fefd8"}, - {file = "django_rq-2.5.1-py2.py3-none-any.whl", hash = "sha256:7be1e10e7091555f9f36edf100b0dbb205ea2b98683d74443d2bdf3c6649a03f"}, + {file = "django-rq-2.8.1.tar.gz", hash = "sha256:ff053aa4d1b1e1acc47c99b4a21b514de8745894c00d1e6f4abc8b37d35d66d6"}, + {file = "django_rq-2.8.1-py2.py3-none-any.whl", hash = "sha256:f5d649dc57b5564011460b2b69c8a60a4f5f10ee8692b51d1dfc17035b1039b8"}, ] [package.dependencies] django = ">=2.0" redis = ">=3" -rq = ">=1.2" +rq = ">=1.14" [package.extras] sentry = ["raven (>=6.1.0)"] @@ -956,30 +974,30 @@ testing = ["mock (>=2.0.0)"] [[package]] name = "django-tables2" -version = "2.4.1" +version = "2.6.0" description = "Table/data-grid framework for Django" optional = false python-versions = "*" files = [ - {file = "django-tables2-2.4.1.tar.gz", hash = "sha256:6c72dd208358539e789e4c0efd7d151e43283a4aa4093a35f44c43489e7ddeaa"}, - {file = "django_tables2-2.4.1-py2.py3-none-any.whl", hash = "sha256:50762bf3d7c61a4eb70e763c3e278650d7266bb78d0497fc8fafcf4e507c9a64"}, + {file = "django-tables2-2.6.0.tar.gz", hash = "sha256:479eed04007cc04bcf764a6fb7a5e3955d94b878ba7f3a4bd4edbd2f7769e08d"}, + {file = "django_tables2-2.6.0-py2.py3-none-any.whl", hash = "sha256:04f23c1181d93716c67085a3c324b449180fd0c5162ef4619acb0b2d9a166133"}, ] [package.dependencies] -Django = ">=1.11" +Django = ">=3.2" [package.extras] tablib = ["tablib"] [[package]] name = "django-taggit" -version = "3.0.0" +version = "4.0.0" description = "django-taggit is a reusable Django application for simple tagging." optional = false python-versions = ">=3.6" files = [ - {file = "django-taggit-3.0.0.tar.gz", hash = "sha256:e645b8e3dd4f85989d5ef5c5a3d5ebbe5badf5d1e51b53e42d0af726240b00b9"}, - {file = "django_taggit-3.0.0-py3-none-any.whl", hash = "sha256:ca2df20399a11321db75988404afb640a08eff61e52bde35f6c16f307004ec9e"}, + {file = "django-taggit-4.0.0.tar.gz", hash = "sha256:4d52de9d37245a9b9f98c0ec71fdccf1d2283e38e8866d40a7ae6a3b6787a161"}, + {file = "django_taggit-4.0.0-py3-none-any.whl", hash = "sha256:eb800dabef5f0a4e047ab0751f82cf805bc4a9e972037ef12bf519f52cd92480"}, ] [package.dependencies] @@ -987,31 +1005,29 @@ Django = ">=3.2" [[package]] name = "django-timezone-field" -version = "4.1.2" -description = "A Django app providing database and form fields for pytz timezone objects." +version = "5.1" +description = "A Django app providing DB, form, and REST framework fields for zoneinfo and pytz timezone objects." optional = false -python-versions = ">=3.5" +python-versions = ">=3.7,<4.0" files = [ - {file = "django-timezone-field-4.1.2.tar.gz", hash = "sha256:cffac62452d060e365938aa9c9f7b72d70d8b26b9c60243bce227b35abd1b9df"}, - {file = "django_timezone_field-4.1.2-py3-none-any.whl", hash = "sha256:897c06e40b619cf5731a30d6c156886a7c64cba3a90364832148da7ef32ccf36"}, + {file = "django_timezone_field-5.1-py3-none-any.whl", hash = "sha256:16ca9955a4e16064e32168b1a0d1cdb2839679c6cb56856c1f49f506e2ca4281"}, + {file = "django_timezone_field-5.1.tar.gz", hash = "sha256:73fc49519273cd5da1c7f16abc04a4bcad87b00cc02968d0d384c0fecf9a8a86"}, ] [package.dependencies] -django = ">=2.2" +"backports.zoneinfo" = {version = ">=0.2.1,<0.3.0", markers = "python_version < \"3.9\""} +Django = ">=2.2,<3.0.dev0 || >=3.2.dev0,<5.0" pytz = "*" -[package.extras] -rest-framework = ["djangorestframework (>=3.0.0)"] - [[package]] name = "django-tree-queries" -version = "0.11.0" +version = "0.15.0" description = "Tree queries with explicit opt-in, without configurability" optional = false python-versions = ">=3.6" files = [ - {file = "django_tree_queries-0.11.0-py3-none-any.whl", hash = "sha256:d74fe9e36dc67cb2c6f7df1969ef700627b0c49af2ada15990dd3ae44e4ddb23"}, - {file = "django_tree_queries-0.11.0.tar.gz", hash = "sha256:768bc75fd5ab617e19bec5c7e207964d7f2f6155f5f3d8c3332b41d9d0e6b436"}, + {file = "django_tree_queries-0.15.0-py3-none-any.whl", hash = "sha256:cf11340de59d3122919fde46e99966bad40ff942df768d683383b111554134a1"}, + {file = "django_tree_queries-0.15.0.tar.gz", hash = "sha256:0e994c2a4601c021a115a397ec8d0ff7d5e614fae95947f72126e6a419c60f08"}, ] [package.extras] @@ -1055,18 +1071,18 @@ pytz = "*" [[package]] name = "drf-spectacular" -version = "0.24.2" +version = "0.26.5" description = "Sane and flexible OpenAPI 3 schema generation for Django REST framework" optional = false python-versions = ">=3.6" files = [ - {file = "drf-spectacular-0.24.2.tar.gz", hash = "sha256:be32417594080a52f996afd83fd47ea9c2b83cbf13f6d3fbf3de809a0dfa7ead"}, - {file = "drf_spectacular-0.24.2-py3-none-any.whl", hash = "sha256:b276e6f7bda6dfb911e742dab87c6e97bc67da2dafe82d6fd8df7cec6c8b03ec"}, + {file = "drf-spectacular-0.26.5.tar.gz", hash = "sha256:aee55330a774ba8a9cbdb125714d1c9ee05a8aafd3ce3be8bfd26527649aeb44"}, + {file = "drf_spectacular-0.26.5-py3-none-any.whl", hash = "sha256:c0002a820b11771fdbf37853deb371947caf0159d1afeeffe7598e964bc1db94"}, ] [package.dependencies] Django = ">=2.2" -djangorestframework = ">=3.10" +djangorestframework = ">=3.10.3" drf-spectacular-sidecar = {version = "*", optional = true, markers = "extra == \"sidecar\""} inflection = ">=0.3.1" jsonschema = ">=2.6.0" @@ -1222,28 +1238,27 @@ test = ["coveralls", "fastdiff (==0.2.0)", "iso8601", "mock", "promise", "pytest [[package]] name = "graphene-django" -version = "2.15.0" +version = "2.16.0" description = "Graphene Django integration" optional = false python-versions = "*" files = [ - {file = "graphene-django-2.15.0.tar.gz", hash = "sha256:b78c9b05bc899016b9cc5bf13faa1f37fe1faa8c5407552c6ddd1a28f46fc31a"}, - {file = "graphene_django-2.15.0-py2.py3-none-any.whl", hash = "sha256:02671d195f0c09c8649acff2a8f4ad4f297d0f7d98ea6e6cdf034b81bab92880"}, + {file = "graphene-django-2.16.0.tar.gz", hash = "sha256:dcf650ebfae52c2e9927d6e8bb005d06366f710b17a015c821c920eda1270566"}, + {file = "graphene_django-2.16.0-py2.py3-none-any.whl", hash = "sha256:ec89469ec94507c1ed998f85ee087d634ec489e20fe08a72893c3ca5e646fc14"}, ] [package.dependencies] -Django = ">=1.11" +Django = ">=2.2" graphene = ">=2.1.7,<3" graphql-core = ">=2.1.0,<3" promise = ">=2.1" singledispatch = ">=3.4.0.3" -six = ">=1.10.0" text-unidecode = "*" [package.extras] -dev = ["black (==19.10b0)", "coveralls", "django-filter (<2)", "django-filter (>=2)", "djangorestframework (>=3.6.3)", "flake8 (==3.7.9)", "flake8-black (==0.1.1)", "flake8-bugbear (==20.1.4)", "mock", "pytest (>=3.6.3)", "pytest-cov", "pytest-django (>=3.3.2)", "pytz"] +dev = ["black (==22.6.0)", "coveralls", "django-filter (>=2)", "djangorestframework (>=3.6.3)", "flake8 (>=5,<6)", "flake8-black (==0.3.3)", "flake8-bugbear (==22.7.1)", "mock", "pytest (>=3.6.3)", "pytest-cov", "pytest-django (>=3.3.2)", "pytz"] rest-framework = ["djangorestframework (>=3.6.3)"] -test = ["coveralls", "django-filter (<2)", "django-filter (>=2)", "djangorestframework (>=3.6.3)", "mock", "pytest (>=3.6.3)", "pytest-cov", "pytest-django (>=3.3.2)", "pytz"] +test = ["coveralls", "django-filter (>=2)", "djangorestframework (>=3.6.3)", "mock", "pytest (>=3.6.3)", "pytest-cov", "pytest-django (>=3.3.2)", "pytz"] [[package]] name = "graphene-django-optimizer" @@ -1457,32 +1472,33 @@ yamlordereddictloader = "*" [[package]] name = "kombu" -version = "5.3.1" +version = "5.4.2" description = "Messaging library for Python." optional = false python-versions = ">=3.8" files = [ - {file = "kombu-5.3.1-py3-none-any.whl", hash = "sha256:48ee589e8833126fd01ceaa08f8a2041334e9f5894e5763c8486a550454551e9"}, - {file = "kombu-5.3.1.tar.gz", hash = "sha256:fbd7572d92c0bf71c112a6b45163153dea5a7b6a701ec16b568c27d0fd2370f2"}, + {file = "kombu-5.4.2-py3-none-any.whl", hash = "sha256:14212f5ccf022fc0a70453bb025a1dcc32782a588c49ea866884047d66e14763"}, + {file = "kombu-5.4.2.tar.gz", hash = "sha256:eef572dd2fd9fc614b37580e3caeafdd5af46c1eff31e7fba89138cdb406f2cf"}, ] [package.dependencies] amqp = ">=5.1.1,<6.0.0" "backports.zoneinfo" = {version = ">=0.2.1", extras = ["tzdata"], markers = "python_version < \"3.9\""} -typing-extensions = {version = "*", markers = "python_version < \"3.10\""} -vine = "*" +typing-extensions = {version = "4.12.2", markers = "python_version < \"3.10\""} +tzdata = {version = "*", markers = "python_version >= \"3.9\""} +vine = "5.1.0" [package.extras] azureservicebus = ["azure-servicebus (>=7.10.0)"] azurestoragequeues = ["azure-identity (>=1.12.0)", "azure-storage-queue (>=12.6.0)"] -confluentkafka = ["confluent-kafka (==2.1.1)"] -consul = ["python-consul2"] +confluentkafka = ["confluent-kafka (>=2.2.0)"] +consul = ["python-consul2 (==0.1.5)"] librabbitmq = ["librabbitmq (>=2.0.0)"] mongodb = ["pymongo (>=4.1.1)"] -msgpack = ["msgpack"] -pyro = ["pyro4"] +msgpack = ["msgpack (==1.1.0)"] +pyro = ["pyro4 (==4.82)"] qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"] -redis = ["redis (>=4.5.2)"] +redis = ["redis (>=4.5.2,!=4.5.5,!=5.0.2)"] slmq = ["softlayer-messaging (>=1.0.3)"] sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"] sqs = ["boto3 (>=1.26.143)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"] @@ -1682,6 +1698,20 @@ profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] +[[package]] +name = "markdown-version-annotations" +version = "1.0.1" +description = "Markdown plugin to add custom admonitions for documenting version differences" +optional = false +python-versions = "<4.0,>=3.7" +files = [ + {file = "markdown_version_annotations-1.0.1-py3-none-any.whl", hash = "sha256:6df0b2ac08bab906c8baa425f59fc0fe342fbe8b3917c144fb75914266b33200"}, + {file = "markdown_version_annotations-1.0.1.tar.gz", hash = "sha256:620aade507ef175ccfb2059db152a34c6a1d2add28c2be16ea4de38d742e6132"}, +] + +[package.dependencies] +markdown = ">=3.3.7,<4.0.0" + [[package]] name = "markupsafe" version = "2.1.3" @@ -1887,17 +1917,6 @@ files = [ {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, ] -[[package]] -name = "mkdocs-version-annotations" -version = "1.0.0" -description = "MkDocs plugin to add custom admonitions for documenting version differences" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "mkdocs-version-annotations-1.0.0.tar.gz", hash = "sha256:6786024b37d27b330fda240b76ebec8e7ce48bd5a9d7a66e99804559d088dffa"}, - {file = "mkdocs_version_annotations-1.0.0-py3-none-any.whl", hash = "sha256:385004eb4a7530dd87a227e08cd907ce7a8fe21fdf297720a4149c511bcf05f5"}, -] - [[package]] name = "mkdocstrings" version = "0.25.2" @@ -1986,66 +2005,67 @@ typing-extensions = ">=4.3.0" [[package]] name = "nautobot" -version = "1.5.16" +version = "1.6.2" description = "Source of truth and network automation platform." optional = false -python-versions = ">=3.7,<4.0" +python-versions = ">=3.8,<3.12" files = [ - {file = "nautobot-1.5.16-py3-none-any.whl", hash = "sha256:9ca3c5e277377155549ffe5a3c2b886ee148c1639fced438eb4f08ba8f7bcec1"}, - {file = "nautobot-1.5.16.tar.gz", hash = "sha256:36b73ffc8b63c2e2667cbc40b05137fd9291335d652adb088c364f546add9adf"}, + {file = "nautobot-1.6.2-py3-none-any.whl", hash = "sha256:513b74633b1b9b75d7211b4e57c1d1ea752fc516d3733125ca6c441245579483"}, + {file = "nautobot-1.6.2.tar.gz", hash = "sha256:16154e6c5ce6fe1b60733761c834f200379f0bd673f1c5516381dd37a919cdb0"}, ] [package.dependencies] -celery = ">=5.2.7,<5.3.0" -Django = ">=3.2.18,<3.3.0" +celery = ">=5.3.1,<5.4.0" +Django = ">=3.2.20,<3.3.0" django-ajax-tables = ">=1.1.1,<1.2.0" -django-cacheops = ">=6.0,<6.1" -django-celery-beat = ">=2.2.1,<2.3.0" -django-constance = {version = ">=2.9.0,<2.10.0", extras = ["database"]} -django-cors-headers = ">=3.13.0,<3.14.0" -django-cryptography = ">=1.0,<1.1" +django-cacheops = ">=6.2,<6.3" +django-celery-beat = ">=2.5.0,<2.6.0" +django-constance = {version = ">=2.9.1,<2.10.0", extras = ["database"]} +django-cors-headers = ">=4.2.0,<4.3.0" +django-cryptography = ">=1.1,<1.2" django-db-file-storage = ">=0.5.5,<0.6.0" -django-extensions = ">=3.2.0,<3.3.0" -django-filter = ">=21.1,<21.2" -django-health-check = ">=3.16.5,<3.17.0" +django-extensions = ">=3.2.3,<3.3.0" +django-filter = ">=23.1,<23.2" +django-health-check = ">=3.17.0,<3.18.0" django-jinja = ">=2.10.2,<2.11.0" django-mptt = ">=0.14.0,<0.15.0" -django-prometheus = ">=2.2.0,<2.3.0" -django-redis = ">=5.2.0,<5.3.0" -django-rq = ">=2.5.1,<2.6.0" -django-tables2 = ">=2.4.1,<2.5.0" -django-taggit = ">=3.0.0,<3.1.0" -django-timezone-field = ">=4.1.2,<4.2.0" -django-tree-queries = ">=0.11,<0.12" +django-prometheus = ">=2.3.1,<2.4.0" +django-redis = ">=5.3.0,<5.4.0" +django-rq = ">=2.8.1,<2.9.0" +django-tables2 = ">=2.6.0,<2.7.0" +django-taggit = ">=4.0.0,<4.1.0" +django-timezone-field = ">=5.1,<5.2" +django-tree-queries = ">=0.15.0,<0.16.0" django-webserver = ">=1.2.0,<1.3.0" djangorestframework = ">=3.14.0,<3.15.0" -drf-spectacular = {version = ">=0.24.2,<0.25.0", extras = ["sidecar"]} +drf-spectacular = {version = ">=0.26.4,<0.27.0", extras = ["sidecar"]} drf-yasg = {version = ">=1.20.0,<2.0.0", extras = ["validation"]} -GitPython = ">=3.1.31,<3.2.0" -graphene-django = ">=2.15.0,<2.16.0" +GitPython = ">=3.1.32,<3.2.0" +graphene-django = ">=2.16.0,<2.17.0" graphene-django-optimizer = ">=0.8.0,<0.9.0" -Jinja2 = ">=3.1.0,<3.2.0" -jsonschema = ">=4.7.0,<4.8.0" +Jinja2 = ">=3.1.2,<3.2.0" +jsonschema = ">=4.7.0,<4.18.0" Markdown = ">=3.3.7,<3.4.0" -MarkupSafe = ">=2.1.1,<2.2.0" +MarkupSafe = ">=2.1.3,<2.2.0" netaddr = ">=0.8.0,<0.9.0" -netutils = ">=1.4.1,<2.0.0" -Pillow = ">=9.3.0,<9.4.0" -prometheus-client = ">=0.14.1,<0.15.0" -psycopg2-binary = ">=2.9.5,<2.10.0" -pycryptodome = ">=3.13.0,<3.14.0" +netutils = ">=1.5.0,<2.0.0" +packaging = ">=23.0,<23.2" +Pillow = ">=10.0.0,<10.1.0" +prometheus-client = ">=0.14.1,<0.18" +psycopg2-binary = ">=2.9.6,<2.10.0" pyuwsgi = ">=2.0.21,<2.1.0" PyYAML = ">=6.0,<6.1" -social-auth-app-django = ">=5.0.0,<5.1.0" +social-auth-app-django = ">=5.2.0,<5.3.0" svgwrite = ">=1.4.2,<1.5.0" +toml = ">=0.10.2,<0.11.0" [package.extras] -all = ["django-auth-ldap (>=4.1.0,<4.2.0)", "django-storages (>=1.12.3,<1.13.0)", "mysqlclient (>=2.1.0,<2.2.0)", "napalm (>=3.4.1,<3.5.0)", "social-auth-core[openidconnect,saml] (>=4.3.0,<4.4.0)"] -ldap = ["django-auth-ldap (>=4.1.0,<4.2.0)"] -mysql = ["mysqlclient (>=2.1.0,<2.2.0)"] -napalm = ["napalm (>=3.4.1,<3.5.0)"] -remote-storage = ["django-storages (>=1.12.3,<1.13.0)"] -sso = ["social-auth-core[openidconnect,saml] (>=4.3.0,<4.4.0)"] +all = ["django-auth-ldap (>=4.3.0,<4.4.0)", "django-storages (>=1.13.2,<1.14.0)", "mysqlclient (>=2.2.0,<2.3.0)", "napalm (>=4.1.0,<4.2.0)", "social-auth-core[openidconnect,saml] (>=4.4.2,<4.5.0)"] +ldap = ["django-auth-ldap (>=4.3.0,<4.4.0)"] +mysql = ["mysqlclient (>=2.2.0,<2.3.0)"] +napalm = ["napalm (>=4.1.0,<4.2.0)"] +remote-storage = ["django-storages (>=1.13.2,<1.14.0)"] +sso = ["social-auth-core[openidconnect,saml] (>=4.4.2,<4.5.0)"] [[package]] name = "ncclient" @@ -2208,76 +2228,69 @@ files = [ [[package]] name = "pillow" -version = "9.3.0" +version = "10.0.1" description = "Python Imaging Library (Fork)" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Pillow-9.3.0-1-cp37-cp37m-win32.whl", hash = "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74"}, - {file = "Pillow-9.3.0-1-cp37-cp37m-win_amd64.whl", hash = "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa"}, - {file = "Pillow-9.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2"}, - {file = "Pillow-9.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de"}, - {file = "Pillow-9.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7"}, - {file = "Pillow-9.3.0-cp310-cp310-win32.whl", hash = "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91"}, - {file = "Pillow-9.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b"}, - {file = "Pillow-9.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20"}, - {file = "Pillow-9.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c"}, - {file = "Pillow-9.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11"}, - {file = "Pillow-9.3.0-cp311-cp311-win32.whl", hash = "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c"}, - {file = "Pillow-9.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef"}, - {file = "Pillow-9.3.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee"}, - {file = "Pillow-9.3.0-cp37-cp37m-win32.whl", hash = "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29"}, - {file = "Pillow-9.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4"}, - {file = "Pillow-9.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4"}, - {file = "Pillow-9.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636"}, - {file = "Pillow-9.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32"}, - {file = "Pillow-9.3.0-cp38-cp38-win32.whl", hash = "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0"}, - {file = "Pillow-9.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc"}, - {file = "Pillow-9.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad"}, - {file = "Pillow-9.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c"}, - {file = "Pillow-9.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448"}, - {file = "Pillow-9.3.0-cp39-cp39-win32.whl", hash = "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48"}, - {file = "Pillow-9.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8"}, - {file = "Pillow-9.3.0.tar.gz", hash = "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] + {file = "Pillow-10.0.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a"}, + {file = "Pillow-10.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff"}, + {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf"}, + {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd"}, + {file = "Pillow-10.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0"}, + {file = "Pillow-10.0.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1"}, + {file = "Pillow-10.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2"}, + {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b"}, + {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1"}, + {file = "Pillow-10.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088"}, + {file = "Pillow-10.0.1-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b"}, + {file = "Pillow-10.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91"}, + {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4"}, + {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08"}, + {file = "Pillow-10.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08"}, + {file = "Pillow-10.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a"}, + {file = "Pillow-10.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7"}, + {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a"}, + {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7"}, + {file = "Pillow-10.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3"}, + {file = "Pillow-10.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849"}, + {file = "Pillow-10.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145"}, + {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2"}, + {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf"}, + {file = "Pillow-10.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d"}, + {file = "Pillow-10.0.1.tar.gz", hash = "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] [[package]] @@ -2430,45 +2443,6 @@ files = [ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] -[[package]] -name = "pycryptodome" -version = "3.13.0" -description = "Cryptographic library for Python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "pycryptodome-3.13.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e468724173df02f9d83f3fea830bf0d04aa291b5add22b4a78e01c97aab04873"}, - {file = "pycryptodome-3.13.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1fb7a6f222072412f320b9e48d3ce981920efbfce37b06d028ec9bd94093b37f"}, - {file = "pycryptodome-3.13.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4f1b594d0cf35bd12ec4244df1155a7f565bf6e6245976ac36174c1564688c90"}, - {file = "pycryptodome-3.13.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:9ea70f6c3f6566159e3798e4593a4a8016994a0080ac29a45200615b45091a1b"}, - {file = "pycryptodome-3.13.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f7aad304575d075faf2806977b726b67da7ba294adc97d878f92a062e357a56a"}, - {file = "pycryptodome-3.13.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:702446a012fd9337b9327d168bb0c7dc714eb93ad361f6f61af9ca8305a301f1"}, - {file = "pycryptodome-3.13.0-cp27-cp27m-win32.whl", hash = "sha256:681ac47c538c64305d710eaed2bb49532f62b3f4c93aa7c423c520df981392e5"}, - {file = "pycryptodome-3.13.0-cp27-cp27m-win_amd64.whl", hash = "sha256:7b3478a187d897f003b2aa1793bcc59463e8d57a42e2aafbcbbe9cd47ec46863"}, - {file = "pycryptodome-3.13.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:eec02d9199af4b1ccfe1f9c587691a07a1fa39d949d2c1dc69d079ab9af8212f"}, - {file = "pycryptodome-3.13.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9c8e0e6c5e982699801b20fa74f43c19aa080d2b53a39f3c132d35958e153bd4"}, - {file = "pycryptodome-3.13.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f5457e44d3f26d9946091e92b28f3e970a56538b96c87b4b155a84e32a40b7b5"}, - {file = "pycryptodome-3.13.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:88d6d54e83cf9bbd665ce1e7b9079983ee2d97a05f42e0569ff00a70f1dd8b1e"}, - {file = "pycryptodome-3.13.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:72de8c4d71e6b11d54528bb924447fa4fdabcbb3d76cc0e7f61d3b6075def6b3"}, - {file = "pycryptodome-3.13.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:008ef2c631f112cd5a58736e0b29f4a28b4bb853e68878689f8b476fd56e0691"}, - {file = "pycryptodome-3.13.0-cp35-abi3-manylinux1_i686.whl", hash = "sha256:51ebe9624ad0a0b4da1aaaa2d43aabadf8537737fd494cee0ffa37cd6326de02"}, - {file = "pycryptodome-3.13.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:deede160bdf87ddb71f0a1314ad5a267b1a960be314ea7dc6b7ad86da6da89a3"}, - {file = "pycryptodome-3.13.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:857c16bffd938254e3a834cd6b2a755ed24e1a953b1a86e33da136d3e4c16a6f"}, - {file = "pycryptodome-3.13.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:ca6db61335d07220de0b665bfee7b8e9615b2dfc67a54016db4826dac34c2dd2"}, - {file = "pycryptodome-3.13.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:073dedf0f9c490ae22ca081b86357646ac9b76f3e2bd89119d137fc697a9e3b6"}, - {file = "pycryptodome-3.13.0-cp35-abi3-win32.whl", hash = "sha256:e3affa03c49cce7b0a9501cc7f608d4f8e61fb2522b276d599ac049b5955576d"}, - {file = "pycryptodome-3.13.0-cp35-abi3-win_amd64.whl", hash = "sha256:e5d72be02b17e6bd7919555811264403468d1d052fa67c946e402257c3c29a27"}, - {file = "pycryptodome-3.13.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:0896d5d15ffe584d46cb9b69a75cf14a2bc8f6daf635b7bf16c1b041342a44b1"}, - {file = "pycryptodome-3.13.0-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:e420cdfca73f80fe15f79bb34756959945231a052440813e5fce531e6e96331a"}, - {file = "pycryptodome-3.13.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:720fafdf3e5c5de93039d8308f765cc60b8e9e7e852ad7135aa65dd89238191f"}, - {file = "pycryptodome-3.13.0-pp27-pypy_73-win32.whl", hash = "sha256:7a8b0e526ff239b4f4c61dd6898e2474d609843ffc437267f3a27ddff626e6f6"}, - {file = "pycryptodome-3.13.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d92a5eddffb0ad39f582f07c1de26e9daf6880e3e782a94bb7ebaf939567f8bf"}, - {file = "pycryptodome-3.13.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:cb9453c981554984c6f5c5ce7682d7286e65e2173d7416114c3593a977a01bf5"}, - {file = "pycryptodome-3.13.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:765b8b16bc1fd699e183dde642c7f2653b8f3c9c1a50051139908e9683f97732"}, - {file = "pycryptodome-3.13.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:b3af53dddf848afb38b3ac2bae7159ddad1feb9bac14aa3acec6ef1797b82f8d"}, - {file = "pycryptodome-3.13.0.tar.gz", hash = "sha256:95bacf9ff7d1b90bba537d3f5f6c834efe6bfbb1a0195cb3573f29e6716ef08d"}, -] - [[package]] name = "pydocstyle" version = "6.3.0" @@ -3179,17 +3153,18 @@ files = [ [[package]] name = "social-auth-app-django" -version = "5.0.0" +version = "5.2.0" description = "Python Social Authentication, Django integration." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "social-auth-app-django-5.0.0.tar.gz", hash = "sha256:b6e3132ce087cdd6e1707aeb1b588be41d318408fcf6395435da0bc6fe9a9795"}, - {file = "social_auth_app_django-5.0.0-py3-none-any.whl", hash = "sha256:52241a25445a010ab1c108bafff21fc5522d5c8cd0d48a92c39c7371824b065d"}, + {file = "social-auth-app-django-5.2.0.tar.gz", hash = "sha256:4a5dae406f3874b4003708ff120c02cb1a4c8eeead56cd163646347309fcd0f8"}, + {file = "social_auth_app_django-5.2.0-py3-none-any.whl", hash = "sha256:0347ca4cd23ea9d15a665da9d22950552fb66b95600e6c2ebae38ca883b3a4ed"}, ] [package.dependencies] -social-auth-core = ">=4.1.0" +Django = ">=3.2" +social-auth-core = ">=4.4.1" [[package]] name = "social-auth-core" @@ -3386,13 +3361,13 @@ docs = ["mkdocs (==1.2.4)", "mkdocs-material (==7.2.2)", "mkdocs-material-extens [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -3436,13 +3411,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "vine" -version = "5.0.0" -description = "Promises, promises, promises." +version = "5.1.0" +description = "Python promises." optional = false python-versions = ">=3.6" files = [ - {file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"}, - {file = "vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"}, + {file = "vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc"}, + {file = "vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0"}, ] [[package]] @@ -3639,7 +3614,10 @@ files = [ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +[extras] +all = [] + [metadata] lock-version = "2.0" -python-versions = "^3.8" -content-hash = "6ea5fde8b200cf914cdd7cf6d6b2a48e0f04e2823cbe15437dc93fb131de6d5c" +python-versions = ">=3.8,<3.12" +content-hash = "cefc1fd34001fd357f2d0ca453d598212232783a19ff019253fbc70b938415f0" diff --git a/pyproject.toml b/pyproject.toml index 6cacb458..22e40c6a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,10 +18,11 @@ packages = [ ] [tool.poetry.dependencies] -python = "^3.8" +python = ">=3.8,<3.12" +# Used for local development +nautobot = "^1.6.0" napalm = ">=2.5.0, <5" zipp = "^3.4.0" -nautobot = "^1.4.0" [tool.poetry.dev-dependencies] bandit = "*" @@ -43,12 +44,16 @@ mkdocs = "1.6.0" # Material for MkDocs theme mkdocs-material = "9.5.32" # Render custom markdown for version added/changed/remove notes -mkdocs-version-annotations = "1.0.0" +markdown-version-annotations = "1.0.1" # Automatic documentation from sources, for MkDocs mkdocstrings = "0.25.2" mkdocstrings-python = "1.10.8" griffe = "1.1.1" +[tool.poetry.extras] +all = [ +] + [tool.black] line-length = 120 target-version = ['py37'] diff --git a/tasks.py b/tasks.py index 971c3b51..9e2710ae 100644 --- a/tasks.py +++ b/tasks.py @@ -1,8 +1,23 @@ -"""Tasks for use with Invoke.""" +"""Tasks for use with Invoke. + +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 + 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 pathlib import Path +from time import sleep + +from invoke.collection import Collection +from invoke.tasks import task as invoke_task def is_truthy(arg): @@ -17,7 +32,14 @@ def is_truthy(arg): """ if isinstance(arg, bool): return arg - return bool(strtobool(arg)) + + val = str(arg).lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return True + elif val in ("n", "no", "f", "false", "off", "0"): + return False + else: + raise ValueError(f"Invalid truthy value: `{arg}`") # Use pyinvoke configuration for default values, see http://docs.pyinvoke.org/en/stable/concepts/configuration.html @@ -26,9 +48,9 @@ def is_truthy(arg): namespace.configure( { "nautobot_device_onboarding": { - "nautobot_ver": "stable", - "project_name": "nautobot_device_onboarding", - "python_ver": "3.8", + "nautobot_ver": "1.6.0", + "project_name": "nautobot-device-onboarding", + "python_ver": "3.11", "local": False, "compose_dir": os.path.join(os.path.dirname(__file__), "development"), "compose_files": [ @@ -43,6 +65,29 @@ def is_truthy(arg): ) +def _is_compose_included(context, name): + return f"docker-compose.{name}.yml" in context.nautobot_device_onboarding.compose_files + + +def _await_healthy_service(context, service): + container_id = docker_compose(context, f"ps -q -- {service}", pty=False, echo=False, hide=True).stdout.strip() + _await_healthy_container(context, container_id) + + +def _await_healthy_container(context, container_id): + while True: + result = context.run( + "docker inspect --format='{{.State.Health.Status}}' " + container_id, + pty=False, + echo=False, + hide=True, + ) + if result.stdout.strip() == "healthy": + break + print(f"Waiting for `{container_id}` container to become healthy ...") + sleep(1) + + def task(function=None, *args, **kwargs): """Task decorator to override the default Invoke task decorator and add each task to the invoke namespace.""" @@ -76,13 +121,28 @@ def docker_compose(context, command, **kwargs): "COMPOSE_HTTP_TIMEOUT": context.nautobot_device_onboarding.compose_http_timeout, "NAUTOBOT_VER": context.nautobot_device_onboarding.nautobot_ver, "PYTHON_VER": context.nautobot_device_onboarding.python_ver, + **kwargs.pop("env", {}), } - compose_command = f'docker compose --project-name {context.nautobot_device_onboarding.project_name} --project-directory "{context.nautobot_device_onboarding.compose_dir}"' + compose_command_tokens = [ + "docker compose", + f"--project-name {context.nautobot_device_onboarding.project_name}", + f'--project-directory "{context.nautobot_device_onboarding.compose_dir}"', + ] + for compose_file in context.nautobot_device_onboarding.compose_files: compose_file_path = os.path.join(context.nautobot_device_onboarding.compose_dir, compose_file) - compose_command += f' -f "{compose_file_path}"' - compose_command += f" {command}" + compose_command_tokens.append(f' -f "{compose_file_path}"') + + compose_command_tokens.append(command) + + # If `service` was passed as a kwarg, add it to the end. + service = kwargs.pop("service", None) + if service is not None: + compose_command_tokens.append(service) + print(f'Running docker compose command "{command}"') + compose_command = " ".join(compose_command_tokens) + return context.run(compose_command, env=build_env, **kwargs) @@ -97,9 +157,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) # ------------------------------------------------------------------------------ @@ -131,42 +193,108 @@ def generate_packages(context): run_command(context, command) +@task( + help={ + "check": ( + "If enabled, check for outdated dependencies in the poetry.lock file, " + "instead of generating a new one. (default: disabled)" + ) + } +) +def lock(context, check=False): + """Generate poetry.lock inside the Nautobot container.""" + run_command(context, f"poetry {'check' if check else '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 -def start(context): - """Start Nautobot and its dependencies in detached mode.""" +@task(help={"service": "If specified, only affect this service."}) +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") + 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): +@task( + aliases=("down",), + help={ + "volumes": "Remove Docker compose volumes (default: True)", + "import-db-file": "Import database from `import-db-file` file into the fresh environment (default: empty)", + }, +) +def destroy(context, volumes=True, import_db_file=""): """Destroy all containers and volumes.""" print("Destroying Nautobot...") - docker_compose(context, "down --volumes") + docker_compose(context, f"down --remove-orphans {'--volumes' if volumes else ''}") + + if not import_db_file: + return + + if not volumes: + raise ValueError("Cannot specify `--no-volumes` and `--import-db-file` arguments at the same time.") + + print(f"Importing database file: {import_db_file}...") + + input_path = Path(import_db_file).absolute() + if not input_path.is_file(): + raise ValueError(f"File not found: {input_path}") + + command = [ + "run", + "--rm", + "--detach", + f"--volume='{input_path}:/docker-entrypoint-initdb.d/dump.sql'", + "--", + "db", + ] + + container_id = docker_compose(context, " ".join(command), pty=False, echo=False, hide=True).stdout.strip() + _await_healthy_container(context, container_id) + print("Stopping database container...") + context.run(f"docker stop {container_id}", pty=False, echo=False, hide=True) + + print("Database import complete, you can start Nautobot with the following command:") + print("invoke start") + + +@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 @@ -179,12 +307,12 @@ 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): +def logs(context, service="", follow=False, tail=0): """View the logs of a docker compose service.""" command = "logs " @@ -193,18 +321,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 @@ -216,7 +347,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") @@ -274,6 +405,179 @@ 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={ + "db-name": "Database name (default: Nautobot database)", + "input-file": "SQL file to execute and quit (default: empty, start interactive CLI)", + "output-file": "Ouput file, overwrite if exists (default: empty, output to stdout)", + "query": "SQL command to execute and quit (default: empty)", + } +) +def dbshell(context, db_name="", input_file="", output_file="", query=""): + """Start database CLI inside the running `db` container. + + Doesn't use `nautobot-server dbshell`, using started `db` service container only. + """ + if input_file and query: + raise ValueError("Cannot specify both, `input_file` and `query` arguments") + if output_file and not (input_file or query): + raise ValueError("`output_file` argument requires `input_file` or `query` argument") + + env = {} + if query: + env["_SQL_QUERY"] = query + + command = [ + "exec", + "--env=_SQL_QUERY" if query else "", + "-- db sh -c '", + ] + + if _is_compose_included(context, "mysql"): + command += [ + "mysql", + "--user=$MYSQL_USER", + "--password=$MYSQL_PASSWORD", + f"--database={db_name or '$MYSQL_DATABASE'}", + ] + elif _is_compose_included(context, "postgres"): + command += [ + "psql", + "--username=$POSTGRES_USER", + f"--dbname={db_name or '$POSTGRES_DB'}", + ] + else: + raise ValueError("Unsupported database backend.") + + command += [ + "'", + '<<<"$_SQL_QUERY"' if query else "", + f"< '{input_file}'" if input_file else "", + f"> '{output_file}'" if output_file else "", + ] + + docker_compose(context, " ".join(command), env=env, pty=not (input_file or output_file or query)) + + +@task( + help={ + "db-name": "Database name to create (default: Nautobot database)", + "input-file": "SQL dump file to replace the existing database with. This can be generated using `invoke backup-db` (default: `dump.sql`).", + } +) +def import_db(context, db_name="", input_file="dump.sql"): + """Stop Nautobot containers and replace the current database with the dump into `db` container.""" + docker_compose(context, "stop -- nautobot worker beat") + start(context, "db") + _await_healthy_service(context, "db") + + command = ["exec -- db sh -c '"] + + if _is_compose_included(context, "mysql"): + if not db_name: + db_name = "$MYSQL_DATABASE" + command += [ + "mysql --user root --password=$MYSQL_ROOT_PASSWORD", + '--execute="', + f"DROP DATABASE IF EXISTS {db_name};", + f"CREATE DATABASE {db_name};", + "" + if db_name == "$MYSQL_DATABASE" + else f"GRANT ALL PRIVILEGES ON {db_name}.* TO $MYSQL_USER; FLUSH PRIVILEGES;", + '"', + "&&", + "mysql", + f"--database={db_name}", + "--user=$MYSQL_USER", + "--password=$MYSQL_PASSWORD", + ] + elif _is_compose_included(context, "postgres"): + if not db_name: + db_name = "$POSTGRES_DB" + command += [ + f"dropdb --if-exists --user=$POSTGRES_USER {db_name} &&", + f"createdb --user=$POSTGRES_USER {db_name} &&", + f"psql --user=$POSTGRES_USER --dbname={db_name}", + ] + else: + raise ValueError("Unsupported database backend.") + + command += [ + "'", + f"< '{input_file}'", + ] + + docker_compose(context, " ".join(command), pty=False) + + print("Database import complete, you can start Nautobot now: `invoke start`") + + +@task( + help={ + "db-name": "Database name to backup (default: Nautobot database)", + "output-file": "Ouput file, overwrite if exists (default: `dump.sql`)", + "readable": "Flag to dump database data in more readable format (default: `True`)", + } +) +def backup_db(context, db_name="", output_file="dump.sql", readable=True): + """Dump database into `output_file` file from `db` container.""" + start(context, "db") + _await_healthy_service(context, "db") + + command = ["exec -- db sh -c '"] + + if _is_compose_included(context, "mysql"): + command += [ + "mysqldump", + "--user=root", + "--password=$MYSQL_ROOT_PASSWORD", + "--skip-extended-insert" if readable else "", + db_name if db_name else "$MYSQL_DATABASE", + ] + elif _is_compose_included(context, "postgres"): + command += [ + "pg_dump", + "--username=$POSTGRES_USER", + f"--dbname={db_name or '$POSTGRES_DB'}", + "--inserts" if readable else "", + ] + else: + raise ValueError("Unsupported database backend.") + + command += [ + "'", + f"> '{output_file}'", + ] + + docker_compose(context, " ".join(command), pty=False) + + print(50 * "=") + print("The database backup has been successfully completed and saved to the following file:") + print(output_file) + print("You can import this database backup with the following command:") + print(f"invoke import-db --input-file '{output_file}'") + print(50 * "=") + + # ------------------------------------------------------------------------------ # DOCS # ------------------------------------------------------------------------------ @@ -283,14 +587,33 @@ def docs(context): command = "mkdocs serve -v" if is_truthy(context.nautobot_device_onboarding.local): - print("Serving Documentation...") + print(">>> Serving Documentation at http://localhost:8001") run_command(context, command) else: - print("Only used when developing locally (i.e. context.nautobot_device_onboarding.local=True)!") + 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 / LINTING +# TESTS # ------------------------------------------------------------------------------ @task( help={ @@ -312,7 +635,7 @@ def black(context, autoformat=False): @task def flake8(context): """Check for PEP8 compliance and other style issues.""" - command = "flake8 ." + command = "flake8 . --config .flake8" run_command(context, command) @@ -349,7 +672,7 @@ def bandit(context): @task def yamllint(context): - """Run yamllint to validate formating adheres to NTC defined YAML standards. + """Run yamllint to validate formatting adheres to NTC defined YAML standards. Args: context (obj): Used to run specific commands @@ -361,7 +684,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) @@ -372,9 +695,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_device_onboarding", failfast=False, buffer=True): +def unittest( + context, + keepdb=False, + label="nautobot_device_onboarding", + failfast=False, + buffer=True, + pattern="", + verbose=False, +): """Run Nautobot unit tests.""" command = f"coverage run --module nautobot.core.cli test {label}" @@ -384,6 +717,11 @@ def unittest(context, keepdb=False, label="nautobot_device_onboarding", failfast command += " --failfast" if buffer: command += " --buffer" + if pattern: + command += f" -k='{pattern}'" + if verbose: + command += " --verbosity 2" + run_command(context, command) @@ -397,10 +735,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_device_onboarding.local): @@ -417,9 +757,16 @@ def tests(context, failfast=False): pydocstyle(context) print("Running yamllint...") yamllint(context) + print("Running poetry check...") + lock(context, check=True) + print("Running migrations check...") + check_migrations(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) + unittest_coverage(context) print("All tests have passed!") - unittest_coverage(context)