diff --git a/.bandit.yml b/.bandit.yml index 4824c564..d42f9460 100644 --- a/.bandit.yml +++ b/.bandit.yml @@ -1,7 +1,7 @@ --- +skips: + - "B404" # Ignore subprocess # No need to check for security issues in the test scripts! exclude_dirs: - "./nautobot_chatops/tests/" - -skips: - - "B404" + - "./.venv/" diff --git a/.cookiecutter.json b/.cookiecutter.json new file mode 100644 index 00000000..0235408c --- /dev/null +++ b/.cookiecutter.json @@ -0,0 +1,35 @@ +{ + "cookiecutter": { + "codeowner_github_usernames": "@glennmatthews @jvanderaa @smk4664 @whitej6", + "full_name": "Network to Code, LLC", + "email": "opensource@networktocode.com", + "github_org": "nautobot", + "plugin_name": "nautobot_chatops", + "verbose_name": "Nautobot ChatOps App", + "plugin_slug": "nautobot-chatops", + "project_slug": "nautobot-plugin-chatops", + "repo_url": "https://github.com/nautobot/nautobot-plugin-chatops/", + "base_url": "chatops", + "min_nautobot_version": "2.0.0", + "max_nautobot_version": "2.9999", + "camel_name": "NautobotChatOpsPlugin", + "project_short_description": "Nautobot ChatOps App", + "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/chatops/en/latest", + "_drift_manager": { + "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", + "template_dir": "nautobot-app", + "template_ref": "develop", + "cookie_dir": "", + "branch_prefix": "drift-manager", + "pull_request_strategy": "create", + "post_actions": [ + "black" + ], + "draft": true, + "baked_commit_ref": "f7199da166a77ef00af4347535a1e09f939a711a" + } + } +} diff --git a/.dockerignore b/.dockerignore index aacfd4e3..2270f496 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,11 +1,27 @@ +# Docker related +development/Dockerfile +development/docker-compose*.yml +development/*.env +*.env +environments/ + +# Python **/*.pyc **/*.pyo -**/*.log +**/__pycache__/ +**/.pytest_cache/ +**/.venv/ + + +# Other +docs/_build +FAQ.md .git/ -.github/ .gitignore -Dockerfile -docker-compose.yml -.env -docs/_build -__pycache__/ \ No newline at end of file +.github +tasks.py +LICENSE +**/*.log +**/.vscode/ +invoke*.yml +tasks.py diff --git a/.flake8 b/.flake8 index 8193a5d0..696795e1 100644 --- a/.flake8 +++ b/.flake8 @@ -1,4 +1,12 @@ [flake8] -# E501: Line length is enforced by Black, so flake8 doesn't need to check it -# W503: Black disagrees with this rule, as does PEP 8; Black wins -ignore = E501, W503, F811, F401 \ No newline at end of file +ignore = + F401, # Module imported but unused + F811, # Redefinition of unused name from line N + E501, # Line length is enforced by Black, so flake8 doesn't need to check it + W503 # Black disagrees with this rule, as does PEP 8; Black wins +exclude = + migrations, + __pycache__, + manage.py, + settings.py, + .venv diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2a57e1af..93878502 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,12 +1,19 @@ --- name: 🐛 Bug Report -about: Report a reproducible bug in the current release of nautobot-plugin-chatops +about: Report a reproducible bug in the current release of nautobot-chatops --- ### Environment -* Python version: -* Nautobot version: -* nautobot-plugin-chatops version: +* Python version: +* Nautobot version: +* nautobot-chatops 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 bbeb5de2..2324a93a 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -5,8 +5,8 @@ about: Propose a new feature or enhancement --- ### Environment -* Nautobot version: -* nautobot-plugin-chatops version: +* Nautobot version: +* nautobot-chatops version: + # Closes: # ## What's Changed + -## TODO +## To Do + - [ ] Explanation of Change(s) -- [ ] Added change log fragment(s) (for more information see [the documentation](https://docs.nautobot.com/projects/chatops/en/latest/dev/contributing/#creating-changelog-fragments)) +- [ ] Added change log fragment(s) (for more information see [the documentation](https://docs.nautobot.com/projects/core/en/stable/development/#creating-changelog-fragments)) - [ ] 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 59d01289..eeb536ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,152 +3,234 @@ name: "CI" concurrency: # Cancel any existing runs of this workflow for this same PR group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true -on: # yamllint disable - - "push" - - "pull_request" +on: # yamllint disable-line rule:truthy rule:comments + push: + branches: + - "main" + - "develop" + tags: + - "v*" + pull_request: ~ + +env: + PLUGIN_NAME: "nautobot-plugin-chatops" jobs: black: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_CHATOPS_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@v2" - with: - python-version: "3.10" + 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_CHATOPS_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@v2" - with: - python-version: "3.10" + uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: bandit" run: "poetry run invoke bandit" - needs: - - "black" pydocstyle: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_CHATOPS_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@v2" - with: - python-version: "3.10" + uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: pydocstyle" run: "poetry run invoke pydocstyle" - needs: - - "black" flake8: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_CHATOPS_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@v2" - with: - python-version: "3.10" + uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: flake8" run: "poetry run invoke flake8" - needs: - - "black" + poetry: + runs-on: "ubuntu-22.04" + env: + INVOKE_NAUTOBOT_CHATOPS_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_CHATOPS_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@v2" - with: - python-version: "3.10" + uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: yamllint" run: "poetry run invoke yamllint" + pylint: needs: + - "bandit" + - "pydocstyle" + - "flake8" + - "poetry" + - "yamllint" - "black" - pylint: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" + strategy: + fail-fast: true + matrix: + python-version: ["3.11"] + nautobot-version: ["2.0.0"] + env: + INVOKE_NAUTOBOT_CHATOPS_PYTHON_VER: "${{ matrix.python-version }}" + INVOKE_NAUTOBOT_CHATOPS_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@v2" + 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: - python-version: "3.10" + 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: "Linting: Pylint" + - name: "Linting: pylint" run: "poetry run invoke pylint" + check-migrations: needs: - "bandit" - "pydocstyle" - "flake8" + - "poetry" - "yamllint" - unittest: + - "black" + runs-on: "ubuntu-22.04" strategy: fail-fast: true matrix: - python-version: ["3.8"] - db-backend: ["postgresql", "mysql"] + python-version: ["3.11"] nautobot-version: ["2.0.0"] - # The include is a method to limit the amount of jobs ran. This essentially - # means that in addition to standard postgres and stable, also the lowest - # supported version and with mysql + env: + INVOKE_NAUTOBOT_CHATOPS_PYTHON_VER: "${{ matrix.python-version }}" + INVOKE_NAUTOBOT_CHATOPS_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.11"] + db-backend: ["postgresql"] + nautobot-version: ["stable"] include: - python-version: "3.11" db-backend: "postgresql" nautobot-version: "2.0.0" - - python-version: "3.11" - db-backend: "postgresql" - nautobot-version: "stable" - python-version: "3.11" db-backend: "mysql" nautobot-version: "stable" - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_CHATOPS_PYTHON_VER: "${{ matrix.python-version }}" INVOKE_NAUTOBOT_CHATOPS_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@v2" + 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: - python-version: "${{ matrix.python-version }}" + 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: "Build Container" - run: "poetry run invoke build" + - name: "Use Mysql invoke settings when needed" + run: "cp invoke.mysql.yml invoke.yml" + if: "matrix.db-backend == 'mysql'" - name: "Run Tests" run: "poetry run invoke unittest" - needs: - - "pylint" publish_gh: + 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" @@ -169,19 +251,19 @@ jobs: tag: "${{ github.ref }}" overwrite: true file_glob: true + publish_pypi: needs: - "unittest" - publish_pypi: 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" @@ -199,20 +281,38 @@ jobs: with: user: "__token__" password: "${{ secrets.PYPI_API_TOKEN }}" + slack-notify: needs: - - "unittest" - - changelog: - if: github.base_ref == 'develop' || github.base_ref == 'next' - runs-on: "ubuntu-20.04" + - "publish_gh" + - "publish_pypi" + runs-on: "ubuntu-22.04" + env: + SLACK_WEBHOOK_URL: "${{ secrets.SLACK_WEBHOOK_URL }}" + SLACK_MESSAGE: >- + *NOTIFICATION: NEW-RELEASE-PUBLISHED*\n + Repository: <${{ github.server_url }}/${{ github.repository }}|${{ github.repository }}>\n + Release: <${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}|${{ github.ref_name }}>\n + Published by: <${{ github.server_url }}/${{ github.actor }}|${{ github.actor }}> steps: - - name: "Check out repository code" - uses: "actions/checkout@v2" + - name: "Send a notification to Slack" + # 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" with: - fetch-depth: "0" - - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" - - name: "Check for changelog entry" - run: | - git fetch --no-tags origin +refs/heads/${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }} - poetry run towncrier check --compare-with origin/${{ github.base_ref }} + payload: | + { + "text": "${{ env.SLACK_MESSAGE }}", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "${{ env.SLACK_MESSAGE }}" + } + } + ] + } + env: + SLACK_WEBHOOK_URL: "${{ secrets.SLACK_WEBHOOK_URL }}" + SLACK_WEBHOOK_TYPE: "INCOMING_WEBHOOK" diff --git a/.github/workflows/rebake.yml b/.github/workflows/rebake.yml new file mode 100644 index 00000000..13d1e3a0 --- /dev/null +++ b/.github/workflows/rebake.yml @@ -0,0 +1,118 @@ +--- +name: "Rebake Cookie" +on: # yamllint disable-line rule:truthy + workflow_call: + inputs: + cookie: + description: "The cookie to rebake" + type: "string" + default: "" + draft: + description: "Whether to create the pull request as a draft" + type: "string" + default: "" + pull-request: + description: "The pull request strategy" + type: "string" + default: "" + template: + description: "The template repository URL" + type: "string" + default: "" + template-dir: + description: "The directory within the template repository to use as the template" + type: "string" + default: "" + template-ref: + description: "The branch or tag to use for the template" + type: "string" + default: "" + drift-manager-tag: + description: "The drift manager Docker image tag to use" + type: "string" + default: "latest" + workflow_dispatch: + inputs: + cookie: + description: "The cookie to rebake" + type: "string" + default: "" + draft: + description: "Whether to create the pull request as a draft" + type: "string" + default: "" + pull-request: + description: "The pull request strategy" + type: "string" + default: "" + template: + description: "The template repository URL" + type: "string" + default: "" + template-dir: + description: "The directory within the template repository to use as the template" + type: "string" + default: "" + template-ref: + description: "The branch or tag to use for the template" + type: "string" + default: "" + drift-manager-tag: + description: "The drift manager Docker image tag to use" + type: "string" + default: "latest" +jobs: + rebake: + runs-on: "ubuntu-22.04" + permissions: + actions: "write" + contents: "write" + packages: "read" + pull-requests: "write" + container: "ghcr.io/nautobot/cookiecutter-nautobot-app-drift-manager/prod:${{ github.event.inputs.drift-manager-tag }}" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + steps: + - name: "Configure Rebake Arguments" + id: "config" + shell: "bash" + run: | + ARGS='--push' + + if [[ '${{ github.event.inputs.draft }}' == 'true' ]]; then + ARGS="$ARGS --draft" + elif [[ '${{ github.event.inputs.draft }}' == 'false' ]]; then + ARGS="$ARGS --no-draft" + elif [[ '${{ github.event.inputs.draft }}' == '' ]]; then + echo "Using repo default value for --draft" + else + echo "ERROR: Invalid value for draft: '${{ github.event.inputs.draft }}'" + exit 1 + fi + + if [[ '${{ github.event.inputs.pull-request }}' != '' ]]; then + ARGS="$ARGS --pull-request='${{ github.event.inputs.pull-request }}'" + fi + + if [[ '${{ github.event.inputs.template }}' != '' ]]; then + ARGS="$ARGS --template='${{ github.event.inputs.template }}'" + fi + + if [[ '${{ github.event.inputs.template-dir }}' != '' ]]; then + ARGS="$ARGS --template-dir='${{ github.event.inputs.template-dir }}'" + fi + + if [[ '${{ github.event.inputs.template-ref }}' != '' ]]; then + ARGS="$ARGS --template-ref='${{ github.event.inputs.template-ref }}'" + fi + + if [[ '${{ github.event.inputs.cookie }}' == '' ]]; then + ARGS="$ARGS '${{ github.repositoryUrl }}'" + else + ARGS="$ARGS '${{ github.event.inputs.cookie }}'" + fi + + echo "args=$ARGS" >> $GITHUB_OUTPUT + - name: "Rebake" + run: | + python -m ntc_cookie_drift_manager rebake ${{ steps.config.outputs.args }} diff --git a/.github/workflows/upstream_testing.yml b/.github/workflows/upstream_testing.yml index f6177d89..b23adc45 100644 --- a/.github/workflows/upstream_testing.yml +++ b/.github/workflows/upstream_testing.yml @@ -1,5 +1,5 @@ --- -name: "Nautobot Upstream Testing" +name: "Nautobot Upstream Monitor" on: # yamllint disable-line rule:truthy rule:comments schedule: @@ -10,4 +10,4 @@ jobs: uses: "nautobot/nautobot/.github/workflows/plugin_upstream_testing_base.yml@develop" with: # Below could potentially be collapsed into a single argument if a concrete relationship between both is enforced invoke_context_name: "NAUTOBOT_CHATOPS" - plugin_name: "nautobot-chatops-plugin" + plugin_name: "nautobot-plugin-chatops" diff --git a/.gitignore b/.gitignore index d1cbef39..ccca6f0a 100644 --- a/.gitignore +++ b/.gitignore @@ -171,7 +171,6 @@ ehthumbs_vista.db # Dump file *.stackdump -dumps/ # Folder config file [Dd]esktop.ini @@ -193,8 +192,28 @@ $RECYCLE.BIN/ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# Jetbrains IDE configs -.idea/ +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries # Gradle and Maven with auto-import # When using Gradle or Maven with auto-import, you should exclude module files, @@ -278,11 +297,13 @@ fabric.properties # Rando creds.env +development/*.txt # Invoke overrides invoke.yml -# Static docs -/nautobot_chatops/static/nautobot_chatops/docs/ +# Docs +public /compose.yaml /dump.sql +/nautobot_chatops/static/nautobot_chatops/docs diff --git a/.pydocstyle.ini b/.pydocstyle.ini deleted file mode 100644 index 71bf7596..00000000 --- a/.pydocstyle.ini +++ /dev/null @@ -1,11 +0,0 @@ -[pydocstyle] -convention = google -inherit = false -match = (?!__init__).*\.py -match-dir = (?!tests)[^\.].* -# D212 is enabled by default in google convention, and complains if we have a docstring like: -# """ -# My docstring is on the line after the opening quotes instead of on the same line as them. -# """ -# We've discussed and concluded that we consider this to be a valid style choice. -add_ignore = D212, D417 \ No newline at end of file diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 882d19e9..9a0a64c7 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,14 +8,13 @@ version: 2 # Set the version of Python in the build environment. build: - os: "ubuntu-20.04" + os: "ubuntu-22.04" tools: - python: "3.9" + python: "3.10" -# MKDocs configuration file mkdocs: configuration: "mkdocs.yml" - # fail_on_warning: true + fail_on_warning: true # Use our docs/requirements.txt during installation. python: diff --git a/.yamllint.yml b/.yamllint.yml index 1cc1bcec..3bcf4764 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -1,13 +1,14 @@ --- extends: "default" -ignore: | - setup_files/ - compose.yaml rules: comments: "enable" - empty-values: "enable" + empty-values: "disable" indentation: indent-sequences: "consistent" line-length: "disable" quoted-strings: quote-type: "double" +ignore: | + .venv/ + compose.yaml + setup_files/ diff --git a/LICENSE b/LICENSE index 2a07d287..d46cc975 100644 --- a/LICENSE +++ b/LICENSE @@ -1,15 +1,15 @@ -Copyright 2020 Network to Code -Network to Code, LLC +Apache Software License 2.0 + +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 +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. \ No newline at end of file +limitations under the License. diff --git a/development/Dockerfile b/development/Dockerfile index a5931cf7..de25f815 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.5.4 -ARG NAUTOBOT_VER="1.5.4" +# Accepts a desired Nautobot version as build argument, default to 2.0.0 +ARG NAUTOBOT_VER="2.0.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_CHATOPS_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 @@ -68,11 +69,13 @@ RUN sort poetry_freeze_base.txt poetry_freeze_all.txt | uniq -u > poetry_freeze_ # 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 .[all] +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/development.env b/development/development.env index b395947b..9d9b4636 100644 --- a/development/development.env +++ b/development/development.env @@ -7,8 +7,6 @@ NAUTOBOT_BANNER_TOP="Local" NAUTOBOT_CHANGELOG_RETENTION=0 NAUTOBOT_DEBUG=True -NAUTOBOT_DJANGO_EXTENSIONS_ENABLED=True -NAUTOBOT_DJANGO_TOOLBAR_ENABLED=True NAUTOBOT_LOG_LEVEL=DEBUG NAUTOBOT_METRICS_ENABLED=True NAUTOBOT_NAPALM_TIMEOUT=5 diff --git a/development/docker-compose.base.yml b/development/docker-compose.base.yml index 680e3786..36ec4b5e 100644 --- a/development/docker-compose.base.yml +++ b/development/docker-compose.base.yml @@ -7,7 +7,7 @@ x-nautobot-build: &nautobot-build context: "../" dockerfile: "development/Dockerfile" x-nautobot-base: &nautobot-base - image: "nautobot-chatops-plugin/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}" + image: "nautobot-chatops/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}" env_file: - "development.env" - "creds.env" @@ -28,7 +28,7 @@ services: 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" healthcheck: diff --git a/development/docker-compose.dev.yml b/development/docker-compose.dev.yml index 1c74cb19..be064971 100644 --- a/development/docker-compose.dev.yml +++ b/development/docker-compose.dev.yml @@ -12,21 +12,28 @@ services: 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 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" - image: "nautobot-chatops-plugin/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}" + - "../:/source" + image: "nautobot-chatops/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" + healthcheck: + test: ["CMD", "true"] # Due to layering, disable: true won't work. Instead, change the test # To expose postgres or redis to the host uncomment the following # postgres: # ports: diff --git a/development/docker-compose.mysql.yml b/development/docker-compose.mysql.yml index 32f1f945..062ada94 100644 --- a/development/docker-compose.mysql.yml +++ b/development/docker-compose.mysql.yml @@ -20,7 +20,7 @@ services: image: "mysql:8" command: - "--default-authentication-plugin=mysql_native_password" - - "--max_connections=200" + - "--max_connections=1000" env_file: - "development.env" - "creds.env" diff --git a/development/docker-compose.postgres.yml b/development/docker-compose.postgres.yml index 8582412b..12d1de31 100644 --- a/development/docker-compose.postgres.yml +++ b/development/docker-compose.postgres.yml @@ -14,7 +14,6 @@ services: - "development.env" - "creds.env" volumes: - # - "./nautobot.sql:/tmp/nautobot.sql" - "postgres_data:/var/lib/postgresql/data" healthcheck: test: "pg_isready --username=$$POSTGRES_USER --dbname=$$POSTGRES_DB" diff --git a/development/nautobot_config.py b/development/nautobot_config.py index c913a18f..8a613ccf 100644 --- a/development/nautobot_config.py +++ b/development/nautobot_config.py @@ -2,9 +2,24 @@ import os import sys -from nautobot.core.settings import * # noqa: F401,F403 pylint: disable=wildcard-import,unused-wildcard-import +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 # @@ -12,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 = { @@ -41,18 +59,28 @@ DATABASES["default"]["OPTIONS"] = {"charset": "utf8mb4"} # -# Debug +# Redis # -DEBUG = True +# The django-redis cache is used to establish concurrent locks using Redis. +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": parse_redis_connection(redis_database=0), + "TIMEOUT": 300, + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, + } +} -# Django Debug Toolbar -DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda _request: DEBUG and not TESTING} +# Redis Cacheops +CACHEOPS_REDIS = parse_redis_connection(redis_database=1) -if DEBUG and "debug_toolbar" not in INSTALLED_APPS: # noqa: F405 - INSTALLED_APPS.append("debug_toolbar") # noqa: F405 -if DEBUG and "debug_toolbar.middleware.DebugToolbarMiddleware" not in MIDDLEWARE: # noqa: F405 - MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") # noqa: F405 +# +# Celery settings are not defined here because they can be overloaded with +# environment variables. By default they use `CACHES["default"]["LOCATION"]`. +# # # Logging @@ -60,10 +88,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, @@ -99,44 +125,17 @@ } # -# 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_capacity_metrics", "nautobot_chatops", ] -# 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_chatops": { # = Common Settings ================== diff --git a/docs/admin/uninstall.md b/docs/admin/uninstall.md index d1a18925..c97c2677 100644 --- a/docs/admin/uninstall.md +++ b/docs/admin/uninstall.md @@ -1,18 +1,31 @@ # Uninstall the App from Nautobot -## Uninstall Guide +Here you will find any steps necessary to cleanly remove the App from your Nautobot environment. -1. Remove Database migrations for ChatOps: +## Database Cleanup - ```bash - nautobot-server migrate nautobot-chatops zero - ``` +Prior to removing the plugin from the `nautobot_config.py`, run the following command to roll back any migration specific to this plugin. -2. Remove the configuration you added in `nautobot_config.py` from `PLUGINS` & `PLUGINS_CONFIG`. -3. Run Nautobot-server post_upgrade +```shell +nautobot-server migrate nautobot_plugin_chatops zero +``` - ```bash - nautobot-server post_ugprade - ``` +## Remove App configuration -4. Restart Nautobot Services +Remove the configuration you added in `nautobot_config.py` from `PLUGINS` & `PLUGINS_CONFIG`. + +## Post Upgrade + +Run Nautobot-server post_upgrade: + +```bash +nautobot-server post_ugprade +``` + +## Restart Services + +Restart Nautobot Services: + +``` +invoke restart +``` diff --git a/docs/admin/upgrade.md b/docs/admin/upgrade.md index 31dde8bb..277ec674 100644 --- a/docs/admin/upgrade.md +++ b/docs/admin/upgrade.md @@ -1,8 +1,10 @@ # Upgrading the App +Here you will find any steps necessary to upgrade the App in your Nautobot environment. + ## Upgrade Guide -When a new release comes out it may be necessary to run a migration of the database to account for any changes in the data models used by this plugin. Execute the command `nautobot-server post_upgrade` from the Nautobot install nautobot/ directory after updating the package. +When a new release comes out it may be necessary to run a migration of the database to account for any changes in the data models used by this plugin. Execute the command `nautobot-server post_upgrade` within the runtime environment of your Nautobot installation after updating the `nautobot-chatops` package via `pip`. ### Upgrading to ChatOps 3.0 diff --git a/docs/assets/extra.css b/docs/assets/extra.css index ce9ea9c8..dfe2e4b1 100644 --- a/docs/assets/extra.css +++ b/docs/assets/extra.css @@ -18,6 +18,15 @@ font-size: 0.7rem; } +/* +* The default max-width is 61rem which does not provide nearly enough space to present code examples or larger tables +*/ +.md-grid { + margin-left: auto; + margin-right: auto; + max-width: 95%; +} + .md-tabs__link { font-size: 0.8rem; } @@ -38,13 +47,8 @@ font-size: 1.2rem; } -/* Keep images in tables at 50px */ -.md-typeset table:not([class]) :is(img) { - height: 50px; -} - img.logo { - height: 100px; + height: 200px; } img.copyright-logo { @@ -91,7 +95,67 @@ a.autorefs-external:hover::after { background-color: var(--md-accent-fg-color); } -/* Do not wrap code blocks in markdown tables. */ -div.md-typeset__table>table>tbody>tr>td>code { - white-space: nowrap; + +/* Customization for mkdocs-version-annotations */ +:root { + /* Icon for "version-added" admonition: Material Design Icons "plus-box-outline" */ + --md-admonition-icon--version-added: url('data:image/svg+xml;charset=utf-8,'); + /* Icon for "version-changed" admonition: Material Design Icons "delta" */ + --md-admonition-icon--version-changed: url('data:image/svg+xml;charset=utf-8,'); + /* Icon for "version-removed" admonition: Material Design Icons "minus-circle-outline" */ + --md-admonition-icon--version-removed: url('data:image/svg+xml;charset=utf-8,'); +} + +/* "version-added" admonition in green */ +.md-typeset .admonition.version-added, +.md-typeset details.version-added { + border-color: rgb(0, 200, 83); +} + +.md-typeset .version-added>.admonition-title, +.md-typeset .version-added>summary { + background-color: rgba(0, 200, 83, .1); +} + +.md-typeset .version-added>.admonition-title::before, +.md-typeset .version-added>summary::before { + background-color: rgb(0, 200, 83); + -webkit-mask-image: var(--md-admonition-icon--version-added); + mask-image: var(--md-admonition-icon--version-added); +} + +/* "version-changed" admonition in orange */ +.md-typeset .admonition.version-changed, +.md-typeset details.version-changed { + border-color: rgb(255, 145, 0); +} + +.md-typeset .version-changed>.admonition-title, +.md-typeset .version-changed>summary { + background-color: rgba(255, 145, 0, .1); +} + +.md-typeset .version-changed>.admonition-title::before, +.md-typeset .version-changed>summary::before { + background-color: rgb(255, 145, 0); + -webkit-mask-image: var(--md-admonition-icon--version-changed); + mask-image: var(--md-admonition-icon--version-changed); +} + +/* "version-removed" admonition in red */ +.md-typeset .admonition.version-removed, +.md-typeset details.version-removed { + border-color: rgb(255, 82, 82); +} + +.md-typeset .version-removed>.admonition-title, +.md-typeset .version-removed>summary { + background-color: rgba(255, 82, 82, .1); +} + +.md-typeset .version-removed>.admonition-title::before, +.md-typeset .version-removed>summary::before { + background-color: rgb(255, 82, 82); + -webkit-mask-image: var(--md-admonition-icon--version-removed); + mask-image: var(--md-admonition-icon--version-removed); } diff --git a/docs/assets/overrides/partials/copyright.html b/docs/assets/overrides/partials/copyright.html index e2c55d12..b92cf5e3 100644 --- a/docs/assets/overrides/partials/copyright.html +++ b/docs/assets/overrides/partials/copyright.html @@ -1,9 +1,10 @@ + @@ -17,4 +18,5 @@ -
\ No newline at end of file +
+ diff --git a/docs/dev/code_reference/api.md b/docs/dev/code_reference/api.md index 01e7ce0a..f2bdd255 100644 --- a/docs/dev/code_reference/api.md +++ b/docs/dev/code_reference/api.md @@ -1,4 +1,4 @@ -# Nautobot Plugin ChatOps API Package +# Nautobot ChatOps App API Package ::: nautobot_chatops.api options: diff --git a/docs/dev/code_reference/package.md b/docs/dev/code_reference/package.md new file mode 100644 index 00000000..664696ae --- /dev/null +++ b/docs/dev/code_reference/package.md @@ -0,0 +1 @@ +::: nautobot_chatops diff --git a/docs/dev/contributing.md b/docs/dev/contributing.md index 57cd6cb3..ef84502f 100644 --- a/docs/dev/contributing.md +++ b/docs/dev/contributing.md @@ -1,4 +1,4 @@ -# Contributing to Nautobot ChatOps +# Contributing to the App The project is packaged with a light [development environment](dev_environment.md) based on `docker-compose` to help with the local development of the project and to run tests. @@ -8,8 +8,7 @@ The project is following Network to Code software development guidelines and is - YAML linting is done with `yamllint`. - Django unit test to ensure the plugin is working properly. -Documentation is built using [mkdocs](https://www.mkdocs.org/). -The [Docker based development environment](dev_environment.md#docker-development-environment) automatically starts a container hosting a live version of the documentation website on [http://localhost:8001](http://localhost:8001) that auto-refreshes when you make any changes to your local files. +Documentation is built using [mkdocs](https://www.mkdocs.org/). The [Docker based development environment](dev_environment.md#docker-development-environment) automatically starts a container hosting a live version of the documentation website on [http://localhost:8001](http://localhost:8001) that auto-refreshes when you make any changes to your local files. ## Creating Changelog Fragments diff --git a/docs/dev/dev_environment.md b/docs/dev/dev_environment.md index 73f88702..b5c9fd66 100644 --- a/docs/dev/dev_environment.md +++ b/docs/dev/dev_environment.md @@ -13,9 +13,9 @@ This is a quick reference guide if you're already familiar with the development The [Invoke](http://www.pyinvoke.org/) library is used to provide some helper commands based on the environment. There are a few configuration parameters which can be passed to Invoke to override the default configuration: -- `nautobot_ver`: the version of Nautobot to use as a base for any built docker containers (default: latest) -- `project_name`: the default docker compose project name (default: `nautobot_chatops`) -- `python_ver`: the version of Python to use as a base for any built docker containers (default: 3.8) +- `nautobot_ver`: the version of Nautobot to use as a base for any built docker containers (default: 2.0.0) +- `project_name`: the default docker compose project name (default: `nautobot-chatops`) +- `python_ver`: the version of Python to use as a base for any built docker containers (default: 3.11) - `local`: a boolean flag indicating if invoke tasks should be run on the host or inside the docker containers (default: False, commands will be run in docker containers) - `compose_dir`: the full path to a directory containing the project compose files - `compose_files`: a list of compose files applied in order (see [Multiple Compose files](https://docs.docker.com/compose/extends/#multiple-compose-files) for more information) @@ -50,8 +50,6 @@ invoke start invoke bootstrap-mattermost ``` -``` - The Nautobot server can now be accessed at [http://localhost:8080](http://localhost:8080) and the live documentation at [http://localhost:8001](http://localhost:8001). To either stop or destroy the development environment use the following options. @@ -67,8 +65,6 @@ To either stop or destroy the development environment use the following options. --- nautobot_chatops: local: true - compose_files: - - "docker-compose.requirements.yml" ``` Run the following commands: @@ -76,7 +72,7 @@ Run the following commands: ```shell poetry shell poetry install --extras nautobot -export $(cat development/dev.env | xargs) +export $(cat development/development.env | xargs) export $(cat development/creds.env | xargs) invoke start && sleep 5 nautobot-server migrate @@ -93,7 +89,7 @@ nautobot-server runserver 0.0.0.0:8080 --insecure Nautobot server can now be accessed at [http://localhost:8080](http://localhost:8080). -It is typically recommended to launch the Nautobot `runserver` command in a separate shell, so you can keep developing and manage the web server separately. +It is typically recommended to launch the Nautobot **runserver** command in a separate shell so you can keep developing and manage the webserver separately. ### Updating the Documentation @@ -111,9 +107,6 @@ The project features a CLI helper based on [Invoke](https://www.pyinvoke.org/) t Each command can be executed with `invoke `. All commands support the arguments `--nautobot-ver` and `--python-ver` if you want to manually define the version of Python and Nautobot to use. Each command also has its own help `invoke --help` -!!! note - To run the MySQL (MariaDB) development environment, set the environment variable as such `export NAUTOBOT_USE_MYSQL=1`. - #### Local Development Environment ``` @@ -146,10 +139,9 @@ Each command can be executed with `invoke `. All commands support the a unittest Run Django unit tests for the plugin. ``` - ## Project Overview -This project provides the ability to develop and manage the Nautobot server locally (with supporting services being dockerized) or by using only Docker containers to manage Nautobot. The main difference between the two environments is the ability to debug and use **pdb** when developing locally. Debugging with **pdb** within the Docker container is more complicated, but can still be accomplished by either entering into the container (via `docker exec`) or attaching your IDE to the container and running the Nautobot service manually within the container. +This project provides the ability to develop and manage the Nautobot server locally (with supporting services being *Dockerized*) or by using only Docker containers to manage Nautobot. The main difference between the two environments is the ability to debug and use **pdb** when developing locally. Debugging with **pdb** within the Docker container is more complicated, but can still be accomplished by either entering into the container (via `docker exec`) or attaching your IDE to the container and running the Nautobot service manually within the container. The upside to having the Nautobot service handled by Docker rather than locally is that you do not have to manage the Nautobot server. The [Docker logs](#docker-logs) provide the majority of the information you will need to help troubleshoot, while getting started quickly and not requiring you to perform several manual steps and remembering to have the Nautobot server running in a separate terminal while you develop. @@ -160,14 +152,14 @@ Follow the directions below for the specific development environment that you ch ## Poetry -Poetry is used in lieu of the `virtualenv` commands and is leveraged in both environments. The virtual environment will provide all the Python packages required to manage the development environment such as **Invoke**. See the [Local Development Environment](#local-poetry-development-environment) section to see how to install Nautobot if you're going to be developing locally (i.e. not using the Docker container). +Poetry is used in lieu of the "virtualenv" commands and is leveraged in both environments. The virtual environment will provide all of the Python packages required to manage the development environment such as **Invoke**. See the [Local Development Environment](#local-poetry-development-environment) section to see how to install Nautobot if you're going to be developing locally (i.e. not using the Docker container). -The `pyproject.toml` file outlines all the relevant dependencies for the project: +The `pyproject.toml` file outlines all of the relevant dependencies for the project: - `tool.poetry.dependencies` - the main list of dependencies. -- `tool.poetry.dev-dependencies` - development dependencies, to facilitate linting, testing, and documentation building. +- `tool.poetry.group.dev.dependencies` - development dependencies, to facilitate linting, testing, and documentation building. -The `poetry shell` command is used to create and enable a virtual environment managed by Poetry, so all commands ran going forward are executed within the virtual environment. This is similar to running the `source venv/bin/activate` command with `virtualenv`. To install project dependencies in the virtual environment, you should run `poetry install` - this will install **both** project and development dependencies. +The `poetry shell` command is used to create and enable a virtual environment managed by Poetry, so all commands ran going forward are executed within the virtual environment. This is similar to running the `source venv/bin/activate` command with virtualenvs. To install project dependencies in the virtual environment, you should run `poetry install` - this will install **both** project and development dependencies. For more details about Poetry and its commands please check out its [online documentation](https://python-poetry.org/docs/). @@ -195,7 +187,7 @@ The first thing you need to do is build the necessary Docker image for Nautobot #14 exporting layers #14 exporting layers 1.2s done #14 writing image sha256:2d524bc1665327faa0d34001b0a9d2ccf450612bf8feeb969312e96a2d3e3503 done -#14 naming to docker.io/nautobot-chatops/nautobot:latest-py3.7 done +#14 naming to docker.io/nautobot-chatops/nautobot:2.0.0-py3.11 done ``` ### Invoke - Starting the Development Environment @@ -221,14 +213,14 @@ Creating nautobot_chatops_worker_1 ... done Docker Compose is now in the Docker CLI, try `docker compose up` ``` -This will start all containers used for hosting Nautobot. You should see the following containers running after `invoke start` is finished. +This will start all of the Docker containers used for hosting Nautobot. You should see the following containers running after `invoke start` is finished. ```bash ➜ docker ps ****CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -ee90fbfabd77 nautobot-chatops/nautobot:latest-py3.7 "nautobot-server rqw…" 16 seconds ago Up 13 seconds nautobot_chatops_worker_1 -b8adb781d013 nautobot-chatops/nautobot:latest-py3.7 "/docker-entrypoint.…" 20 seconds ago Up 15 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp nautobot_chatops_nautobot_1 -d64ebd60675d nautobot-chatops/nautobot:latest-py3.7 "mkdocs serve -v -a …" 25 seconds ago Up 18 seconds 0.0.0.0:8001->8080/tcp, :::8001->8080/tcp nautobot_chatops_docs_1 +ee90fbfabd77 nautobot-chatops/nautobot:2.0.0-py3.11 "nautobot-server rqw…" 16 seconds ago Up 13 seconds nautobot_chatops_worker_1 +b8adb781d013 nautobot-chatops/nautobot:2.0.0-py3.11 "/docker-entrypoint.…" 20 seconds ago Up 15 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp nautobot_chatops_nautobot_1 +d64ebd60675d nautobot-chatops/nautobot:2.0.0-py3.11 "mkdocs serve -v -a …" 25 seconds ago Up 18 seconds 0.0.0.0:8001->8080/tcp, :::8001->8080/tcp nautobot_chatops_docs_1 e72d63129b36 postgres:13-alpine "docker-entrypoint.s…" 25 seconds ago Up 19 seconds 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp nautobot_chatops_postgres_1 96c6ff66997c redis:6-alpine "docker-entrypoint.s…" 25 seconds ago Up 21 seconds 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp nautobot_chatops_redis_1 ``` @@ -243,14 +235,14 @@ Once the containers are fully up, you should be able to open up a web browser, a ### Invoke - Creating a Superuser -The Nautobot development image will automatically provision a super-user when specifying the following variables within `creds.env` which is the default when copying `creds.example.env` to `creds.env`. +The Nautobot development image will automatically provision a super user when specifying the following variables within `creds.env` which is the default when copying `creds.example.env` to `creds.env`. - `NAUTOBOT_CREATE_SUPERUSER=true` - `NAUTOBOT_SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567` - `NAUTOBOT_SUPERUSER_PASSWORD=admin` !!! note - The default username is **admin**, but can be overridden by specifying `NAUTOBOT_SUPERUSER_USERNAME`. + The default username is **admin**, but can be overridden by specifying **NAUTOBOT_SUPERUSER_USERNAME**. If you need to create additional superusers, run the follow commands. @@ -313,7 +305,7 @@ The magic here is the root directory is mounted inside your Docker containers wh !!! warning There are a few exceptions to this, as outlined in the section [To Rebuild or Not To Rebuild](#to-rebuild-or-not-to-rebuild). -The back-end Django process is set up to automatically reload itself (it only takes a couple of seconds) every time a file is updated (saved). So for example, if you were to update one of the files like `tables.py`, then save it, the changes will be visible right away in the web browser! +The back-end Django process is setup to automatically reload itself (it only takes a couple of seconds) every time a file is updated (saved). So for example, if you were to update one of the files like `tables.py`, then save it, the changes will be visible right away in the web browser! !!! note You may get connection refused while Django reloads, but it should be refreshed fairly quickly. @@ -327,7 +319,10 @@ When trying to debug an issue, one helpful thing you can look at are the logs wi ``` !!! note - The `--follow` tag will keep the logs open, and output them in real-time as they are generated. + The `--follow` argument will keep the logs open, and output them in real-time as they are generated. + +!!! info + Want to limit the log output even further? Use the `--tail <#>` command line argument in conjunction with `-f`. So for example, `invoke logs --service nautobot --follow` will follow logs from `nautobot` docker compose service. You can find all running services via `invoke ps`. @@ -404,14 +399,14 @@ namespace.configure( { "nautobot_chatops": { ... - "python_ver": "3.7", + "python_ver": "3.11", ... } } ) ``` -Or set the `INVOKE_NAUTOBOT_GOLDEN_CONFIG_PYTHON_VER` variable. +Or set the `INVOKE_NAUTOBOT_CHATOPS_PYTHON_VER` variable. ### Updating Nautobot Version @@ -423,7 +418,7 @@ namespace.configure( { "nautobot_chatops": { ... - "nautobot_ver": "1.0.2", + "nautobot_ver": "2.0.0", ... } } diff --git a/docs/index.md b/docs/index.md index f8e5c92f..f32fd72b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3,4 +3,4 @@ hide: - navigation --- ---8<-- "README.md" \ No newline at end of file +--8<-- "README.md" diff --git a/docs/requirements.txt b/docs/requirements.txt index 0da7e270..d168c88f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,8 +1,5 @@ -griffe==0.30.1 -mkdocs==1.3.1 -mkdocs-material==8.4.2 +mkdocs==1.5.2 +mkdocs-material==9.1.15 mkdocs-version-annotations==1.0.0 +mkdocstrings-python==1.5.2 mkdocstrings==0.22.0 -mkdocstrings-python==1.1.2 -Jinja2==3.0.3 -mkdocs-include-markdown-plugin==3.6.1 \ No newline at end of file diff --git a/docs/user/app_getting_started.md b/docs/user/app_getting_started.md index c2da634b..a49f69d3 100644 --- a/docs/user/app_getting_started.md +++ b/docs/user/app_getting_started.md @@ -1,10 +1,10 @@ # Getting Started with the App -A step-by-step tutorial on how to get the App going and how to use it. +This document provides a step-by-step tutorial on how to get the App going and how to use it. ## Install the App -To install the App, please follow the instructions detailed in the [Administrator Guide](../admin/install/index.md). +To install the App, please follow the instructions detailed in the [Installation Guide](../admin/install/index.md). ## Link Nautobot Account diff --git a/docs/user/app_overview.md b/docs/user/app_overview.md index b2af3cca..aaf87b1a 100644 --- a/docs/user/app_overview.md +++ b/docs/user/app_overview.md @@ -1,8 +1,11 @@ # App Overview -The ChatOps plugin is a Nautobot Plugin that provides a Chatbot framework for Nautobot. +This document provides an overview of the App including critical information and import considerations when applying it to your Nautobot environment. -## Description/Overview +!!! note + Throughout this documentation, the terms "app" and "plugin" will be used interchangeably. + +## Description The ChatOps framework provides Network Engineers power to query Nautobot or their Network (through ChatOps plugins) while staying within their preferred Chat application. The goal of ChatOps is to bring people, processes and Automation together. diff --git a/docs/user/app_faq.md b/docs/user/faq.md similarity index 100% rename from docs/user/app_faq.md rename to docs/user/faq.md diff --git a/invoke.example.yml b/invoke.example.yml index 90872991..f9e42e74 100644 --- a/invoke.example.yml +++ b/invoke.example.yml @@ -1,9 +1,9 @@ --- nautobot_chatops: project_name: "nautobot-chatops" - nautobot_ver: "1.5.4" + nautobot_ver: "2.0.0" local: false - python_ver: "3.8" + python_ver: "3.11" compose_dir: "development" compose_files: - "docker-compose.base.yml" diff --git a/invoke.mysql.yml b/invoke.mysql.yml index 20cebb59..5002f6c9 100644 --- a/invoke.mysql.yml +++ b/invoke.mysql.yml @@ -1,9 +1,9 @@ --- nautobot_chatops: project_name: "nautobot-chatops" - nautobot_ver: "1.5.4" + nautobot_ver: "2.0.0" local: false - python_ver: "3.8" + python_ver: "3.11" compose_dir: "development" compose_files: - "docker-compose.base.yml" diff --git a/mkdocs.yml b/mkdocs.yml index 0f8d2034..032b6c26 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,11 +1,11 @@ --- dev_addr: "127.0.0.1:8001" -edit_uri: "edit/develop/docs" +edit_uri: "edit/main/nautobot-plugin-chatops/docs" site_dir: "nautobot_chatops/static/nautobot_chatops/docs" -site_name: "Nautobot ChatOps Documentation" -site_url: "https://docs.nautobot.com/projects/chatops/en/stable/" -repo_url: "https://github.com/nautobot/nautobot-plugin-chatops" -copyright: "Copyright © 2020 Network to Code" +site_name: "Nautobot ChatOps App Documentation" +site_url: "https://docs.nautobot.com/projects/chatops/en/latest/" +repo_url: "https://github.com/nautobot/nautobot-plugin-chatops/" +copyright: "Copyright © The Authors" theme: name: "material" navigation_depth: 4 @@ -14,16 +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" - - "navigation.footer" - - "search.suggest" + - "navigation.tracking" - "search.highlight" - "search.share" - - "navigation.indexes" - - "content.action.edit" - - "content.action.view" + - "search.suggest" favicon: "assets/favicon.ico" logo: "assets/nautobot_logo.svg" palette: @@ -84,7 +85,6 @@ markdown_extensions: - "footnotes" plugins: - "search" - - "include-markdown" - "mkdocs-version-annotations" - "mkdocstrings": default_handler: "python" @@ -101,7 +101,7 @@ nav: - User Guide: - App Overview: "user/app_overview.md" - Getting Started: "user/app_getting_started.md" - - Frequently Asked Questions: "user/app_faq.md" + - Frequently Asked Questions: "user/faq.md" - Integrations: - "user/aci_commands.md" - "user/ansible_commands.md" @@ -151,6 +151,7 @@ nav: - Glossary: "glossary.md" - Code Reference: - "dev/code_reference/index.md" + - Package: "dev/code_reference/package.md" - Models: "dev/code_reference/models.md" - API: "dev/code_reference/api.md" - Dispatchers: @@ -161,4 +162,5 @@ nav: - "dev/code_reference/mattermost.md" - "models/accessgrant.md" - "models/commandtoken.md" + - "models/chatopsaccountlink.md" - Nautobot Docs Home ↗︎: "https://docs.nautobot.com" diff --git a/nautobot_chatops/__init__.py b/nautobot_chatops/__init__.py index dd1c8c6f..6fcfa54d 100644 --- a/nautobot_chatops/__init__.py +++ b/nautobot_chatops/__init__.py @@ -1,9 +1,6 @@ -"""Nautobot plugin implementing a chatbot.""" -try: - from importlib import metadata -except ImportError: - # Python version < 3.8 - import importlib_metadata as metadata +"""Plugin declaration for nautobot_chatops.""" +# Metadata is inherited from Nautobot. If not including Nautobot in the environment, this should be added +from importlib import metadata __version__ = metadata.version(__name__) diff --git a/nautobot_chatops/api/__init__.py b/nautobot_chatops/api/__init__.py index e69de29b..81763b58 100644 --- a/nautobot_chatops/api/__init__.py +++ b/nautobot_chatops/api/__init__.py @@ -0,0 +1 @@ +"""REST API module for nautobot_chatops plugin.""" diff --git a/nautobot_chatops/dispatchers/adaptive_cards.py b/nautobot_chatops/dispatchers/adaptive_cards.py index d325c59d..e1dd5ff3 100644 --- a/nautobot_chatops/dispatchers/adaptive_cards.py +++ b/nautobot_chatops/dispatchers/adaptive_cards.py @@ -201,7 +201,7 @@ def select_element(self, action_id, choices, default=(None, None), confirm=False Args: action_id (str): Identifying string to associate with this element choices (list): List of (display, value) tuples - default (tuple: Default (display, value) to preselect + default (tuple): Default (display, value) to preselect confirm (bool): If true (and the platform supports it), prompt the user to confirm their selection """ return { diff --git a/nautobot_chatops/dispatchers/base.py b/nautobot_chatops/dispatchers/base.py index 6204a378..9bf67da6 100644 --- a/nautobot_chatops/dispatchers/base.py +++ b/nautobot_chatops/dispatchers/base.py @@ -53,7 +53,7 @@ def user(self): ).nautobot_user except ObjectDoesNotExist: logger.warning( - "Could not find User matching %s - id: %s." "Add a ChatOps User to link the accounts.", + "Could not find User matching %s - id: %s. Add a ChatOps User to link the accounts.", self.context["user_name"], self.context["user_id"], ) @@ -240,7 +240,6 @@ def multi_input_dialog(self, command, sub_command, dialog_title, dialog_list): """ raise NotImplementedError - # pylint: disable=no-self-use def needs_permission_to_send_image(self): """Return True if this bot needs to ask the user for permission to post an image.""" return False @@ -326,17 +325,14 @@ def user_mention(self): """Markup for a mention of the username/userid specified in our context.""" raise NotImplementedError - # pylint: disable=no-self-use def bold(self, text): """Mark text as bold.""" return f"**{text}**" - # pylint: disable=no-self-use def hyperlink(self, text, url): """Create Hyperlinks.""" return f"[{text}]({url})" - # pylint: disable=no-self-use def monospace(self, text): """Mark text as monospace.""" return f"`{text}`" @@ -367,7 +363,7 @@ def select_element(self, action_id, choices, default=(None, None), confirm=False Args: action_id (str): Identifying string to associate with this element choices (list): List of (display, value) tuples - default (tuple: Default (display, value) to preselect + default (tuple): Default (display, value) to preselect confirm (bool): If true (and the platform supports it), prompt the user to confirm their selection """ raise NotImplementedError diff --git a/nautobot_chatops/dispatchers/mattermost.py b/nautobot_chatops/dispatchers/mattermost.py index bc111dd6..5303f2a1 100644 --- a/nautobot_chatops/dispatchers/mattermost.py +++ b/nautobot_chatops/dispatchers/mattermost.py @@ -606,7 +606,6 @@ def actions_block(self, block_id, actions): # Leaving in place to pass the testing. return {"block_id": block_id, "actions": actions} - # pylint: disable=no-self-use def _input_block(self, block_id, label, element): """Construct a block consisting of Input elements.""" element["display_name"] = label diff --git a/nautobot_chatops/dispatchers/ms_teams.py b/nautobot_chatops/dispatchers/ms_teams.py index d49a9919..ead1e4c2 100644 --- a/nautobot_chatops/dispatchers/ms_teams.py +++ b/nautobot_chatops/dispatchers/ms_teams.py @@ -44,7 +44,7 @@ def user(self): ).nautobot_user except ObjectDoesNotExist: logger.warning( - "Could not find User matching %s - id: %s." "Add a ChatOps User to link the accounts.", + "Could not find User matching %s - id: %s. Add a ChatOps User to link the accounts.", self.context["user_name"], self.context["user_ad_id"], ) diff --git a/nautobot_chatops/dispatchers/slack.py b/nautobot_chatops/dispatchers/slack.py index c52d29aa..82ce52fc 100644 --- a/nautobot_chatops/dispatchers/slack.py +++ b/nautobot_chatops/dispatchers/slack.py @@ -315,7 +315,6 @@ def send_exception(self, exception): text=f"Sorry @{self.context.get('user_name')}, an error occurred :sob:\n```{exception}```", ) - # pylint: disable=no-self-use def delete_message(self, response_url): """Delete a message that was previously sent.""" WebhookClient(response_url).send_dict({"delete_original": "true"}) @@ -506,7 +505,7 @@ def select_element(self, action_id, choices, default=(None, None), confirm=False Args: action_id (str): Identifying string to associate with this element choices (list): List of (display, value) tuples - default (tuple: Default (display, value) to preselect + default (tuple): Default (display, value) to preselect confirm (bool): If true (and the platform supports it), prompt the user to confirm their selection """ data = { diff --git a/nautobot_chatops/integrations/aci/aci.py b/nautobot_chatops/integrations/aci/aci.py index 247a4694..572401fa 100644 --- a/nautobot_chatops/integrations/aci/aci.py +++ b/nautobot_chatops/integrations/aci/aci.py @@ -204,6 +204,7 @@ def get_static_path(self, tenant: str, ap: str, epg: str) -> list: ) sp_list = [] for obj in resp.json()["imdata"]: + # pylint: disable-next=use-dict-literal sp_dict = dict(encap=obj["fvRsPathAtt"]["attributes"]["encap"]) # pylint: disable-next=invalid-name tDn = obj["fvRsPathAtt"]["attributes"]["tDn"] @@ -273,6 +274,7 @@ def get_static_path(self, tenant: str, ap: str, epg: str) -> list: def get_epg_details(self, tenant: str, ap: str, epg: str) -> dict: """Return EPG configuration details.""" resp = self._get(f"/api/node/mo/uni/tn-{tenant}/ap-{ap}/epg-{epg}.json?query-target=children") + # pylint: disable-next=use-dict-literal epg_dict = dict(bd=None, subnets=[], provided_contracts=[], consumed_contracts=[], domains=[], static_paths=[]) epg_dict["name"] = epg for obj in resp.json()["imdata"]: @@ -281,6 +283,7 @@ def get_epg_details(self, tenant: str, ap: str, epg: str) -> dict: epg_dict["subnets"] = self.get_bd_subnet(tenant, epg_dict["bd"]) if "fvRsCons" in obj: epg_dict["consumed_contracts"].append( + # pylint: disable-next=use-dict-literal dict( name=obj["fvRsCons"]["attributes"]["tnVzBrCPName"], filters=self.get_contract_filters(tenant, obj["fvRsCons"]["attributes"]["tnVzBrCPName"]), @@ -288,6 +291,7 @@ def get_epg_details(self, tenant: str, ap: str, epg: str) -> dict: ) if "fvRsProv" in obj: epg_dict["provided_contracts"].append( + # pylint: disable-next=use-dict-literal dict( name=obj["fvRsProv"]["attributes"]["tnVzBrCPName"], filters=self.get_contract_filters(tenant, obj["fvRsProv"]["attributes"]["tnVzBrCPName"]), diff --git a/nautobot_chatops/integrations/ansible/tower.py b/nautobot_chatops/integrations/ansible/tower.py index 4f69ffdc..bd6e5c04 100644 --- a/nautobot_chatops/integrations/ansible/tower.py +++ b/nautobot_chatops/integrations/ansible/tower.py @@ -10,6 +10,8 @@ _CONFIG = settings.PLUGINS_CONFIG["nautobot_chatops"] +DEFAULT_TIMEOUT = 20 + def _get_uri(uri): """Validate URI schema and no trailing slash. @@ -79,6 +81,7 @@ def _launch_job(self, template_name, extra_vars): headers=self.headers, data=json.dumps({"extra_vars": extra_vars}), verify=self.tower_verify_ssl, # nosec + timeout=DEFAULT_TIMEOUT, ) response.raise_for_status() logger.info("Job submission to Ansible Tower:") @@ -100,6 +103,7 @@ def _get_tower(self, api_path, **kwargs): auth=(self.username, self.password), **kwargs, verify=self.tower_verify_ssl, # nosec + timeout=DEFAULT_TIMEOUT, ) return response.json() diff --git a/nautobot_chatops/integrations/aristacv/cvpgrpcutils.py b/nautobot_chatops/integrations/aristacv/cvpgrpcutils.py index 9e1d0da5..7a0ca77d 100644 --- a/nautobot_chatops/integrations/aristacv/cvpgrpcutils.py +++ b/nautobot_chatops/integrations/aristacv/cvpgrpcutils.py @@ -25,7 +25,10 @@ def connect_cv(settings): cert = bytes(ssl.get_server_certificate((cvp_host, 8443)), "utf-8") channel_creds = grpc.ssl_channel_credentials(cert) response = requests.post( - f"https://{cvp_host}/cvpservice/login/authenticate.do", auth=(username, password), verify=False # nosec + f"https://{cvp_host}/cvpservice/login/authenticate.do", + auth=(username, password), + verify=False, # nosec + timeout=DEFAULT_TIMEOUT, ) # Otherwise, the server is expected to have a valid certificate signed by a well-known CA. else: diff --git a/nautobot_chatops/integrations/aristacv/utils.py b/nautobot_chatops/integrations/aristacv/utils.py index 70ff1818..456987f3 100644 --- a/nautobot_chatops/integrations/aristacv/utils.py +++ b/nautobot_chatops/integrations/aristacv/utils.py @@ -560,6 +560,7 @@ def get_token_crt(): f"https://{CVP_HOST}/cvpservice/login/authenticate.do", auth=(CVP_USERNAME, CVP_PASSWORD), verify=False, # nosec + timeout=DEFAULT_TIMEOUT, ) else: request = requests.post( diff --git a/nautobot_chatops/integrations/grafana/filters.py b/nautobot_chatops/integrations/grafana/filters.py index be7bf0c0..51e4aa6b 100644 --- a/nautobot_chatops/integrations/grafana/filters.py +++ b/nautobot_chatops/integrations/grafana/filters.py @@ -18,7 +18,7 @@ class Meta: fields = ("dashboard_slug", "dashboard_uid", "friendly_name") - def search(self, queryset, name, value): # pylint: disable=unused-argument,no-self-use + def search(self, queryset, name, value): # pylint: disable=unused-argument """Perform the filtered search.""" if not value.strip(): return queryset @@ -40,7 +40,7 @@ class Meta: fields = ("dashboard", "command_name", "friendly_name", "panel_id") - def search(self, queryset, name, value): # pylint: disable=unused-argument,no-self-use + def search(self, queryset, name, value): # pylint: disable=unused-argument """Perform the filtered search.""" if not value.strip(): return queryset @@ -66,7 +66,7 @@ class Meta: fields = ("panel", "name", "friendly_name", "query", "modelattr", "value", "response") - def search(self, queryset, name, value): # pylint: disable=unused-argument,no-self-use + def search(self, queryset, name, value): # pylint: disable=unused-argument """Perform the filtered search.""" if not value.strip(): return queryset diff --git a/nautobot_chatops/integrations/meraki/worker.py b/nautobot_chatops/integrations/meraki/worker.py index 5b71502a..6d08f2c2 100644 --- a/nautobot_chatops/integrations/meraki/worker.py +++ b/nautobot_chatops/integrations/meraki/worker.py @@ -28,6 +28,7 @@ except KeyError as err: MERAKI_DASHBOARD_API_KEY = os.getenv("MERAKI_DASHBOARD_API_KEY") if not MERAKI_DASHBOARD_API_KEY: + # pylint: disable-next=broad-exception-raised raise Exception("Unable to find the Meraki API key.") from err @@ -570,6 +571,7 @@ def configure_basic_access_port( # pylint: disable=too-many-arguments dialog_list=dialog_list, ) return False + # pylint: disable-next=use-dict-literal port_params = dict(name=port_desc, enabled=bool(enabled), type="access", vlan=vlan) LOGGER.info("PORT PARMS: %s", port_params) client = MerakiClient(api_key=MERAKI_DASHBOARD_API_KEY) diff --git a/nautobot_chatops/integrations/panorama/constant.py b/nautobot_chatops/integrations/panorama/constant.py index 17967d43..1748f8f1 100644 --- a/nautobot_chatops/integrations/panorama/constant.py +++ b/nautobot_chatops/integrations/panorama/constant.py @@ -22,3 +22,4 @@ PANOS_MANUFACTURER_NAME = "Palo Alto Networks" PANOS_PLATFORM = "PANOS" PANOS_DEVICE_ROLE = "Firewall" +DEFAULT_TIMEOUT = 20 diff --git a/nautobot_chatops/integrations/panorama/utils.py b/nautobot_chatops/integrations/panorama/utils.py index e8157d9a..4d82f8b3 100644 --- a/nautobot_chatops/integrations/panorama/utils.py +++ b/nautobot_chatops/integrations/panorama/utils.py @@ -12,7 +12,7 @@ from panos.policies import PostRulebase, PreRulebase, Rulebase, SecurityRule from requests.exceptions import RequestException -from .constant import PLUGIN_CFG +from .constant import DEFAULT_TIMEOUT, PLUGIN_CFG logger = logging.getLogger(__name__) @@ -31,7 +31,12 @@ def get_api_key_api(url: str = PLUGIN_CFG["panorama_host"]) -> str: params = {"type": "keygen", "user": PLUGIN_CFG["panorama_user"], "password": PLUGIN_CFG["panorama_password"]} - response = requests.get(f"https://{url}/api/", params=params, verify=False) # nosec + response = requests.get( + f"https://{url}/api/", + params=params, + verify=False, # nosec + timeout=DEFAULT_TIMEOUT, + ) if response.status_code != 200: raise RequestException(f"Something went wrong while making a request. Reason: {response.text}") @@ -218,7 +223,12 @@ def _get_pcap(capture_filename: str, ip_address: str): params = {"key": get_api_key_api(), "type": "export", "category": "filters-pcap", "from": "1.pcap"} - respone = requests.get(url, params=params, verify=False) # nosec + respone = requests.get( + url, + params=params, + verify=False, # nosec + timeout=DEFAULT_TIMEOUT, + ) with open(capture_filename, "wb") as pcap_file: pcap_file.write(respone.content) diff --git a/nautobot_chatops/sockets/slack.py b/nautobot_chatops/sockets/slack.py index bf1f6c22..6d8dd1bb 100644 --- a/nautobot_chatops/sockets/slack.py +++ b/nautobot_chatops/sockets/slack.py @@ -15,11 +15,11 @@ from nautobot_chatops.utils import socket_check_and_enqueue_command -async def main(): # pylint: disable=too-many-statements +# pylint: disable-next=too-many-statements +async def main(): """Slack Socket Main Loop.""" - SLASH_PREFIX = settings.PLUGINS_CONFIG["nautobot_chatops"].get( # pylint:disable=invalid-name - "slack_slash_command_prefix" - ) + # pylint: disable-next=invalid-name + SLASH_PREFIX = settings.PLUGINS_CONFIG["nautobot_chatops"].get("slack_slash_command_prefix") client = SocketModeClient( app_token=settings.PLUGINS_CONFIG["nautobot_chatops"].get("slack_app_token"), web_client=AsyncWebClient(token=settings.PLUGINS_CONFIG["nautobot_chatops"]["slack_api_token"]), @@ -78,7 +78,7 @@ async def process_slash_command(client, req): return await socket_check_and_enqueue_command(registry, command, subcommand, params, context, SlackDispatcher) - # pylint: disable=too-many-locals,too-many-return-statements,too-many-branches,too-many-statements,too-many-nested-blocks + # pylint: disable-next=too-many-locals,too-many-return-statements,too-many-branches,too-many-statements async def process_interactive(client, req): client.logger.debug("Processing interactive.") payload = req.payload @@ -125,6 +125,7 @@ async def process_interactive(client, req): # Nothing more to do return + # pylint: disable-next=too-many-nested-blocks elif "view" in payload and payload["view"]: # View submission triggered from a modal dialog client.logger.info("Submission triggered from a modal dialog") diff --git a/nautobot_chatops/tests/__init__.py b/nautobot_chatops/tests/__init__.py index 90a42e97..102ef4fd 100644 --- a/nautobot_chatops/tests/__init__.py +++ b/nautobot_chatops/tests/__init__.py @@ -1 +1 @@ -"""Test for the nautobot_chatops plugin.""" +"""Unit tests for nautobot_chatops plugin.""" diff --git a/nautobot_chatops/tests/aci/test_aci.py b/nautobot_chatops/tests/aci/test_aci.py index 2126f813..1385399a 100644 --- a/nautobot_chatops/tests/aci/test_aci.py +++ b/nautobot_chatops/tests/aci/test_aci.py @@ -1,5 +1,5 @@ """Tests for integrations.aci.aci.""" -# pylint: disable=no-self-use, import-outside-toplevel, invalid-name +# pylint: disable=invalid-name import unittest from unittest.mock import patch, Mock from nautobot_chatops.integrations.aci.aci import NautobotPluginChatopsAci, RequestHTTPError diff --git a/nautobot_chatops/tests/meraki/test_utils.py b/nautobot_chatops/tests/meraki/test_utils.py index 31680cdd..3a14a80b 100644 --- a/nautobot_chatops/tests/meraki/test_utils.py +++ b/nautobot_chatops/tests/meraki/test_utils.py @@ -9,7 +9,7 @@ class TestUtils(unittest.TestCase): """Test Version is the same.""" @patch("nautobot_chatops.integrations.meraki.utils.MerakiClient.get_meraki_orgs") - def test_org_name_to_id(self, mock_orgs): # pylint: disable=no-self-use + def test_org_name_to_id(self, mock_orgs): """Test Translate Org Name to Org Id.""" mock_orgs.return_value = [ { @@ -23,7 +23,7 @@ def test_org_name_to_id(self, mock_orgs): # pylint: disable=no-self-use assert client.org_name_to_id("NTC-TEST") == "123456" @patch("nautobot_chatops.integrations.meraki.utils.MerakiClient.get_meraki_devices") - def test_name_to_serial(self, mock_devices): # pylint: disable=no-self-use + def test_name_to_serial(self, mock_devices): """Test Translate Name to Serial.""" mock_devices.return_value = [ { @@ -45,7 +45,7 @@ def test_name_to_serial(self, mock_devices): # pylint: disable=no-self-use assert client.name_to_serial("NTC-TEST", "fw01-test") == "SN123456" @patch("nautobot_chatops.integrations.meraki.utils.MerakiClient.get_meraki_networks_by_org") - def test_netname_to_id(self, mock_net_name): # pylint: disable=no-self-use + def test_netname_to_id(self, mock_net_name): """Translate Network Name to Network ID.""" mock_net_name.return_value = [ { diff --git a/nautobot_chatops/tests/test_api.py b/nautobot_chatops/tests/test_api.py index eb319a70..3113ce4e 100644 --- a/nautobot_chatops/tests/test_api.py +++ b/nautobot_chatops/tests/test_api.py @@ -1,9 +1,5 @@ """Test cases for Nautobot Chatops API.""" -try: - from importlib import metadata -except ImportError: - # Python version < 3.8 - import importlib_metadata as metadata +from importlib import metadata from django.urls import reverse diff --git a/nautobot_chatops/tests/test_basic.py b/nautobot_chatops/tests/test_basic.py new file mode 100644 index 00000000..9a096567 --- /dev/null +++ b/nautobot_chatops/tests/test_basic.py @@ -0,0 +1,34 @@ +"""Basic tests that do not require Django.""" +import unittest +import os +import toml + +from nautobot_chatops import __version__ as project_version + + +class TestVersion(unittest.TestCase): + """Test Version is the same.""" + + def test_version(self): + """Verify that pyproject.toml version is same as version specified in the package.""" + parent_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) + poetry_version = toml.load(os.path.join(parent_path, "pyproject.toml"))["tool"]["poetry"]["version"] + self.assertEqual(project_version, poetry_version) + + +class TestDocsPackaging(unittest.TestCase): + """Test Version in doc requirements is the same pyproject.""" + + def test_version(self): + """Verify that pyproject.toml dev dependencies have the same versions as in the docs requirements.txt.""" + parent_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) + poetry_path = os.path.join(parent_path, "pyproject.toml") + poetry_details = toml.load(poetry_path)["tool"]["poetry"]["group"]["dev"]["dependencies"] + with open(f"{parent_path}/docs/requirements.txt", "r", encoding="utf-8") as file: + requirements = [line for line in file.read().splitlines() if (len(line) > 0 and not line.startswith("#"))] + for pkg in requirements: + if len(pkg.split("==")) == 2: + pkg, version = pkg.split("==") + else: + version = "*" + self.assertEqual(poetry_details[pkg], version) diff --git a/nautobot_chatops/tests/test_utils.py b/nautobot_chatops/tests/test_utils.py index 413fcbcb..8d5276e2 100644 --- a/nautobot_chatops/tests/test_utils.py +++ b/nautobot_chatops/tests/test_utils.py @@ -144,7 +144,6 @@ def test_default_deny(self, mock_enqueue_task): self.assertIsNone(MockDispatcher.error) mock_enqueue_task.assert_called_once() - # pylint: disable=no-self-use def setup_db(self): """Per-testcase database population for most test cases.""" # Create some globally applicable access grants: diff --git a/poetry.lock b/poetry.lock index 4303eae6..c55799d9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiodns" @@ -183,6 +183,17 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"] test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.22)"] +[[package]] +name = "appnope" +version = "0.1.3" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = "*" +files = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] + [[package]] name = "arrow" version = "1.2.3" @@ -216,20 +227,39 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] name = "astroid" -version = "2.11.7" +version = "2.15.8" description = "An abstract syntax tree for Python with inference support." optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.7.2" files = [ - {file = "astroid-2.11.7-py3-none-any.whl", hash = "sha256:86b0a340a512c65abf4368b80252754cda17c02cdbbd3f587dddf98112233e7b"}, - {file = "astroid-2.11.7.tar.gz", hash = "sha256:bb24615c77f4837c707669d16907331374ae8a964650a66999da3f5ca68dc946"}, + {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, + {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, ] [package.dependencies] lazy-object-proxy = ">=1.4.0" -setuptools = ">=20.0" -typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} -wrapt = ">=1.11,<2" +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} +wrapt = [ + {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, + {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, +] + +[[package]] +name = "asttokens" +version = "2.4.0" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.0-py2.py3-none-any.whl", hash = "sha256:cf8fc9e61a86461aa9fb161a14a0841a03c405fa829ac6b202670b3495d2ce69"}, + {file = "asttokens-2.4.0.tar.gz", hash = "sha256:2e0171b991b2c959acc6c49318049236844a5da1d65ba2672c4880c1c894834e"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +test = ["astroid", "pytest"] [[package]] name = "async-timeout" @@ -260,6 +290,17 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +optional = false +python-versions = "*" +files = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] + [[package]] name = "backports-zoneinfo" version = "0.2.1" @@ -868,6 +909,17 @@ requests = {version = ">=2.27.0", extras = ["socks"]} [package.extras] dev = ["check-manifest", "coverage", "pep8", "pyflakes", "pylint", "pyyaml"] +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + [[package]] name = "deepdiff" version = "6.5.0" @@ -1041,6 +1093,21 @@ files = [ [package.dependencies] Django = "*" +[[package]] +name = "django-debug-toolbar" +version = "4.2.0" +description = "A configurable set of panels that display various debug information about the current request/response." +optional = false +python-versions = ">=3.8" +files = [ + {file = "django_debug_toolbar-4.2.0-py3-none-any.whl", hash = "sha256:af99128c06e8e794479e65ab62cc6c7d1e74e1c19beb44dcbf9bad7a9c017327"}, + {file = "django_debug_toolbar-4.2.0.tar.gz", hash = "sha256:bc7fdaafafcdedefcc67a4a5ad9dac96efd6e41db15bc74d402a54a2ba4854dc"}, +] + +[package.dependencies] +django = ">=3.2.4" +sqlparse = ">=0.2" + [[package]] name = "django-extensions" version = "3.2.3" @@ -1328,6 +1395,20 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "executing" +version = "2.0.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = "*" +files = [ + {file = "executing-2.0.0-py2.py3-none-any.whl", hash = "sha256:06df6183df67389625f4e763921c6cf978944721abf3e714000200aab95b0657"}, + {file = "executing-2.0.0.tar.gz", hash = "sha256:0ff053696fdeef426cda5bd18eacd94f82c91f49823a2e9090124212ceea9b08"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + [[package]] name = "flake8" version = "3.9.2" @@ -1577,13 +1658,13 @@ six = ">=1.12" [[package]] name = "griffe" -version = "0.30.1" +version = "0.36.7" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "griffe-0.30.1-py3-none-any.whl", hash = "sha256:b2f3df6952995a6bebe19f797189d67aba7c860755d3d21cc80f64d076d0154c"}, - {file = "griffe-0.30.1.tar.gz", hash = "sha256:007cc11acd20becf1bb8f826419a52b9d403bbad9d8c8535699f5440ddc0a109"}, + {file = "griffe-0.36.7-py3-none-any.whl", hash = "sha256:7a09f8e9b97c7ebe227f6529a298bf7e0e742a9837ee261cc8260d50b4aa039f"}, + {file = "griffe-0.36.7.tar.gz", hash = "sha256:a6fe60b16720ca0cf63c9e667a4c05eea40dfe4abcf114741885f945b74c7071"}, ] [package.dependencies] @@ -1854,6 +1935,45 @@ files = [ httpx = ">=0.23.0,<0.24.0" PyJWT = ">=2.4.0,<3.0.0" +[[package]] +name = "ipython" +version = "8.12.3" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ipython-8.12.3-py3-none-any.whl", hash = "sha256:b0340d46a933d27c657b211a329d0be23793c36595acf9e6ef4164bc01a1804c"}, + {file = "ipython-8.12.3.tar.gz", hash = "sha256:3910c4b54543c2ad73d06579aa771041b7d5707b033bd488669b4cf544e3b363"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" +typing-extensions = {version = "*", markers = "python_version < \"3.10\""} + +[package.extras] +all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] + [[package]] name = "isodate" version = "0.6.1" @@ -1899,6 +2019,25 @@ pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib" plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] +[[package]] +name = "jedi" +version = "0.19.1" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + [[package]] name = "jinja2" version = "3.1.2" @@ -2168,6 +2307,20 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.5" +files = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] + +[package.dependencies] +traitlets = "*" + [[package]] name = "mccabe" version = "0.6.1" @@ -2218,29 +2371,34 @@ files = [ [[package]] name = "mkdocs" -version = "1.3.1" +version = "1.5.2" description = "Project documentation with Markdown." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "mkdocs-1.3.1-py3-none-any.whl", hash = "sha256:fda92466393127d2da830bc6edc3a625a14b436316d1caf347690648e774c4f0"}, - {file = "mkdocs-1.3.1.tar.gz", hash = "sha256:a41a2ff25ce3bbacc953f9844ba07d106233cd76c88bac1f59cb1564ac0d87ed"}, + {file = "mkdocs-1.5.2-py3-none-any.whl", hash = "sha256:60a62538519c2e96fe8426654a67ee177350451616118a41596ae7c876bb7eac"}, + {file = "mkdocs-1.5.2.tar.gz", hash = "sha256:70d0da09c26cff288852471be03c23f0f521fc15cf16ac89c7a3bfb9ae8d24f9"}, ] [package.dependencies] -click = ">=3.3" +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} ghp-import = ">=1.0" -importlib-metadata = ">=4.3" -Jinja2 = ">=2.10.2" -Markdown = ">=3.2.1,<3.4" +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +jinja2 = ">=2.11.1" +markdown = ">=3.2.1" +markupsafe = ">=2.0.1" mergedeep = ">=1.3.4" packaging = ">=20.5" -PyYAML = ">=3.10" +pathspec = ">=0.11.1" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" pyyaml-env-tag = ">=0.1" watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] [[package]] name = "mkdocs-autorefs" @@ -2257,39 +2415,27 @@ files = [ Markdown = ">=3.3" mkdocs = ">=1.1" -[[package]] -name = "mkdocs-include-markdown-plugin" -version = "3.6.1" -description = "Mkdocs Markdown includer plugin." -optional = false -python-versions = ">=3.6" -files = [ - {file = "mkdocs_include_markdown_plugin-3.6.1-py3-none-any.whl", hash = "sha256:ea36a7d50ee98028f03574d7bf40a307e16211ad3013a4e42f64494b3c106e9e"}, - {file = "mkdocs_include_markdown_plugin-3.6.1.tar.gz", hash = "sha256:5e7416f23081085a220f7534b2fc7456e74c5a65f3b401da1f29b9e9132b46e5"}, -] - -[package.extras] -dev = ["bump2version (==1.0.1)", "flake8 (==3.9.2)", "flake8-implicit-str-concat (==0.2.0)", "flake8-print (==4.0.0)", "isort (==5.9.1)", "mdpo (==0.3.61)", "mkdocs (==1.2.3)", "pre-commit (==2.13.0)", "pytest (==6.2.5)", "pytest-cov (==3.0.0)", "pyupgrade (==2.19.4)", "yamllint (==1.26.1)"] -test = ["pytest (==6.2.5)", "pytest-cov (==3.0.0)"] - [[package]] name = "mkdocs-material" -version = "8.4.2" +version = "9.1.15" description = "Documentation that simply works" optional = false python-versions = ">=3.7" files = [ - {file = "mkdocs-material-8.4.2.tar.gz", hash = "sha256:704c64c3fff126a3923c2961d95f26b19be621342a6a4e49ed039f0bb7a5c540"}, - {file = "mkdocs_material-8.4.2-py2.py3-none-any.whl", hash = "sha256:166287bb0e4197804906bf0389a852d5ced43182c30127ac8b48a4e497ecd7e5"}, + {file = "mkdocs_material-9.1.15-py3-none-any.whl", hash = "sha256:b49e12869ab464558e2dd3c5792da5b748a7e0c48ee83b4d05715f98125a7a39"}, + {file = "mkdocs_material-9.1.15.tar.gz", hash = "sha256:8513ab847c9a541ed3d11a3a7eed556caf72991ee786c31c5aac6691a121088a"}, ] [package.dependencies] -jinja2 = ">=3.0.2" +colorama = ">=0.4" +jinja2 = ">=3.0" markdown = ">=3.2" -mkdocs = ">=1.3.0" -mkdocs-material-extensions = ">=1.0.3" -pygments = ">=2.12" -pymdown-extensions = ">=9.4" +mkdocs = ">=1.4.2" +mkdocs-material-extensions = ">=1.1" +pygments = ">=2.14" +pymdown-extensions = ">=9.9.1" +regex = ">=2022.4.24" +requests = ">=2.26" [[package]] name = "mkdocs-material-extensions" @@ -2341,17 +2487,17 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "1.1.2" +version = "1.5.2" description = "A Python handler for mkdocstrings." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocstrings_python-1.1.2-py3-none-any.whl", hash = "sha256:c2b652a850fec8e85034a9cdb3b45f8ad1a558686edc20ed1f40b4e17e62070f"}, - {file = "mkdocstrings_python-1.1.2.tar.gz", hash = "sha256:f28bdcacb9bcdb44b6942a5642c1ea8b36870614d33e29e3c923e204a8d8ed61"}, + {file = "mkdocstrings_python-1.5.2-py3-none-any.whl", hash = "sha256:ed37ca6d216986e2ac3530c19c3e7be381d1e3d09ea414e4ff467d6fd2cbd9c1"}, + {file = "mkdocstrings_python-1.5.2.tar.gz", hash = "sha256:81eb4a93bc454a253daf247d1a11397c435d641c64fa165324c17c06170b1dfb"}, ] [package.dependencies] -griffe = ">=0.24" +griffe = ">=0.35" mkdocstrings = ">=0.20" [[package]] @@ -2734,6 +2880,21 @@ all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1 gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] invoke = ["invoke (>=2.0)"] +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + [[package]] name = "pathspec" version = "0.11.2" @@ -2756,6 +2917,31 @@ files = [ {file = "pbr-5.11.1.tar.gz", hash = "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"}, ] +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +optional = false +python-versions = "*" +files = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] + [[package]] name = "pillow" version = "10.0.0" @@ -3028,6 +3214,31 @@ files = [ {file = "psycopg2_binary-2.9.8-cp39-cp39-win_amd64.whl", hash = "sha256:1f279ba74f0d6b374526e5976c626d2ac3b8333b6a7b08755c513f4d380d3add"}, ] +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] + +[package.extras] +tests = ["pytest"] + [[package]] name = "pycares" version = "4.3.0" @@ -3230,27 +3441,32 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pylint" -version = "2.13.9" +version = "2.17.7" description = "python code static checker" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.7.2" files = [ - {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"}, - {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"}, + {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, + {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, ] [package.dependencies] -astroid = ">=2.11.5,<=2.12.0-dev0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -dill = ">=0.2" +astroid = ">=2.15.8,<=2.17.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = [ + {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, +] isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] -testutil = ["gitpython (>3)"] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] [[package]] name = "pylint-django" @@ -3444,7 +3660,7 @@ six = ">=1.5" name = "python-dotenv" version = "0.21.1" description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "python-dotenv-0.21.1.tar.gz", hash = "sha256:1c93de8f636cde3ce377292818d0e440b6e45a82f215c3744979151fa8151c49"}, @@ -3633,6 +3849,103 @@ async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2 hiredis = ["hiredis (>=1.0.0)"] ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] +[[package]] +name = "regex" +version = "2023.10.3" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.7" +files = [ + {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, + {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, + {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, + {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, + {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, + {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, + {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, + {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, + {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, + {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, + {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, + {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, + {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, + {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, + {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, + {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, +] + [[package]] name = "requests" version = "2.31.0" @@ -3799,7 +4112,8 @@ files = [ {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win32.whl", hash = "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e"}, - {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_12_6_arm64.whl", hash = "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win32.whl", hash = "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122"}, @@ -4030,6 +4344,25 @@ dev = ["build", "flake8"] doc = ["sphinx"] test = ["pytest", "pytest-cov"] +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + [[package]] name = "stevedore" version = "5.1.0" @@ -4123,7 +4456,7 @@ files = [ name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -optional = true +optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, @@ -4141,6 +4474,17 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "tomlkit" +version = "0.12.1" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"}, + {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, +] + [[package]] name = "towncrier" version = "22.8.0" @@ -4163,6 +4507,21 @@ tomli = "*" [package.extras] dev = ["packaging"] +[[package]] +name = "traitlets" +version = "5.11.2" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.11.2-py3-none-any.whl", hash = "sha256:98277f247f18b2c5cabaf4af369187754f4fb0e85911d473f72329db8a7f4fae"}, + {file = "traitlets-5.11.2.tar.gz", hash = "sha256:7564b5bf8d38c40fa45498072bf4dc5e8346eb087bbf1e2ae2d8774f6a0f078e"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.5.1)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] + [[package]] name = "types-protobuf" version = "3.20.4.6" @@ -4596,4 +4955,4 @@ panorama = ["defusedxml", "ipaddr", "netmiko", "netutils", "pan-os-python"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12" -content-hash = "4c11a5938fe94cefce25e82a6d2fbdcb03a1342eef3ebb8a4193d8cae10b4721" +content-hash = "f5f3df8e929a05526ed64e2f2e2820312bf770072f731044afb556e7900fd223" diff --git a/pyproject.toml b/pyproject.toml index 1cf5dde0..4e609703 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,16 +3,26 @@ name = "nautobot-chatops" version = "3.0.1" description = "A plugin providing chatbot capabilities for Nautobot" authors = ["Network to Code, LLC "] +license = "Apache-2.0" readme = "README.md" -homepage = "https://github.com/nautobot/nautobot-plugin-chatops" -repository = "https://github.com/nautobot/nautobot-plugin-chatops" -documentation = "https://docs.nautobot.com/projects/chatops/en/stable/" +homepage = "https://github.com/nautobot/nautobot-plugin-chatops/" +repository = "https://github.com/nautobot/nautobot-plugin-chatops/" keywords = ["nautobot", "nautobot-plugin"] +classifiers = [ + "Intended Audience :: Developers", + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] include = [ "LICENSE", "README.md", - # Poetry by default will exclude files that are in .gitignore - "nautobot_chatops/static/nautobot_chatops/docs/**/*", +] +packages = [ + { include = "nautobot_chatops" }, ] [tool.poetry.plugins."nautobot.workers"] @@ -59,34 +69,28 @@ termcolor = { version = "1.1.0", optional = true } texttable = "^1.6.2" webexteamssdk = "^1.3" -[tool.poetry.dev-dependencies] -black = "*" -yamllint = "*" +[tool.poetry.group.dev.dependencies] bandit = "*" -# Pinning older pylint due to https://github.com/pylint-dev/pylint/issues/7381 -pylint = "2.13.9" -pylint-django = "*" -pydocstyle = "*" -prybar = "*" +black = "*" +coverage = "~5.4" +django-debug-toolbar = "*" +flake8 = "*" invoke = "*" -flake8 = "^3.9.2" -griffe = "0.30.1" -# Rendering docs to HTML -mkdocs = "1.3.1" -# Material for mkdocs theme -mkdocs-material = "8.4.2" -# Automatic documentation from sources, for MkDocs +ipython = "*" +mkdocs = "1.5.2" +mkdocs-material = "9.1.15" +mkdocs-version-annotations = "1.0.0" mkdocstrings = "0.22.0" -mkdocstrings-python = "1.1.2" -# Render custom markdown for version added/changed/remove notes -mkdocs-version-annotations = "~1.0.0" -# Allow Markdown files to include other files -mkdocs-include-markdown-plugin = "~3.6.1" -python-dotenv = "^0.21.1" -# Change log management and generation -towncrier = "~22.8.0" -coverage = "~5.4" +mkdocstrings-python = "1.5.2" +prybar = "*" +pydocstyle = "*" +pylint = "*" +pylint-django = "*" +pylint-nautobot = "^0.2.0" requests-mock = "^1.9.3" +toml = "*" +towncrier = "~22.8.0" +yamllint = "*" [tool.poetry.extras] all = [ @@ -146,12 +150,9 @@ panorama = [ ] nautobot = ["nautobot"] -[tool.poetry.group.dev.dependencies] -pylint-nautobot = "^0.2.0" - [tool.black] line-length = 120 -target-version = ['py36'] +target-version = ['py38', 'py39', 'py310', 'py311'] include = '\.pyi?$' exclude = ''' ( @@ -173,53 +174,54 @@ exclude = ''' ''' [tool.pylint.master] -# Including the pylint_django plugin -load-plugins=["pylint_nautobot", "pylint_django"] - -[tool.pylint.message_control] -disable=""", - django-not-configured, - too-few-public-methods, - too-many-lines, - too-many-ancestors, - nb-incorrect-base-class, - """ +# Include the pylint_django plugin to avoid spurious warnings about Django patterns +load-plugins="pylint_django, pylint_nautobot" +ignore=".venv" + +[tool.pylint.basic] +# No docstrings required for private methods (Pylint default), or for test_ functions, or for inner Meta classes. +no-docstring-rgx="^(_|test_|Meta$)" + +[tool.pylint.messages_control] +# Line length is enforced by Black, so pylint doesn't need to check it. +# Pylint and Black disagree about how to format multi-line arrays; Black wins. +disable = [ + "line-too-long", + "nb-incorrect-base-class", + "too-few-public-methods", + "too-many-ancestors", + "too-many-arguments", + "too-many-lines", +] [tool.pylint.miscellaneous] -notes=""", +# Don't flag TODO as a failure, let us commit with things that still need to be done in the code +notes = """, FIXME, XXX, """ -[tool.pylint.design] -max-args=6 -max-public-methods=22 - [tool.pylint.similarities] -ignore-imports= true -min-similarity-lines=0 - -[tool.pylint.format] -max-line-length=120 +ignore-imports = true +min-similarity-lines = 0 [tool.pylint-nautobot] -supported_nautobot_versions = ["2",] +supported_nautobot_versions = [ + "2.0.0" +] + +[tool.pydocstyle] +convention = "google" +inherit = false +match = "(?!__init__).*\\.py" +match-dir = "(?!tests|migrations|development)[^\\.].*" +# D212 is enabled by default in google convention, and complains if we have a docstring like: +# """ +# My docstring is on the line after the opening quotes instead of on the same line as them. +# """ +# We've discussed and concluded that we consider this to be a valid style choice. +add_ignore = "D212" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry_core>=1.0.0"] build-backend = "poetry.core.masonry.api" - -[tool.towncrier] -package = "nautobot_chatops" -directory = "changes" -filename = "docs/admin/release_notes/version_3.0.md" -template = "development/towncrier_template.j2" -start_string = "" -issue_format = "[#{issue}](https://github.com/nautobot/nautobot-plugin-chatops/issues/{issue})" - -[tool.towncrier.fragment.added] -[tool.towncrier.fragment.changed] -[tool.towncrier.fragment.deprecated] -[tool.towncrier.fragment.fixed] -[tool.towncrier.fragment.removed] -[tool.towncrier.fragment.security] diff --git a/tasks.py b/tasks.py index e8674a6a..7f09ee8b 100644 --- a/tasks.py +++ b/tasks.py @@ -1,6 +1,6 @@ """Tasks for use with Invoke. -(c) 2020-2021 Network To Code +Copyright (c) 2023, Network to Code, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -12,16 +12,10 @@ limitations under the License. """ -from distutils.util import strtobool -from invoke import Collection, task as invoke_task import os -from dotenv import load_dotenv - - -def _load_dotenv(): - load_dotenv("./development/development.env") - load_dotenv("./development/creds.env") +from invoke.collection import Collection +from invoke.tasks import task as invoke_task def is_truthy(arg): @@ -36,7 +30,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 @@ -47,7 +48,7 @@ def is_truthy(arg): "nautobot_chatops": { "nautobot_ver": "2.0.0", "project_name": "nautobot-chatops", - "python_ver": "3.10", + "python_ver": "3.11", "local": False, "compose_dir": os.path.join(os.path.dirname(__file__), "development"), "compose_files": [ @@ -64,6 +65,10 @@ def is_truthy(arg): ) +def _is_compose_included(context, name): + return f"docker-compose.{name}.yml" in context.nautobot_chatops.compose_files + + def task(function=None, *args, **kwargs): """Task decorator to override the default Invoke task decorator and add each task to the invoke namespace.""" @@ -169,10 +174,17 @@ def generate_packages(context): run_command(context, command) -@task -def lock(context): +@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, "poetry lock --no-update") + run_command(context, f"poetry {'check' if check else 'lock --no-update'}") # ------------------------------------------------------------------------------ @@ -360,160 +372,166 @@ def exec(context, service="nautobot", command="bash", 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)", - "input": "SQL file to execute and quit (default: empty)", - "output": "Ouput file, overwrite if exists (default: empty)", } ) -def dbshell(context, query="", input="", output=""): +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 and query: - raise ValueError("Cannot specify both, `input` and `query` arguments") - if output and not (input or query): - raise ValueError("`output` argument requires `input` or `query` argument") + 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") - _load_dotenv() + env = {} + if query: + env["_SQL_QUERY"] = query - service = "db" - env_vars = {} - command = ["exec"] + command = [ + "exec", + "--env=_SQL_QUERY" if query else "", + "-- db sh -c '", + ] - if "docker-compose.mysql.yml" in context.nautobot_chatops.compose_files: - env_vars["MYSQL_PWD"] = os.getenv("MYSQL_PASSWORD") + if _is_compose_included(context, "mysql"): command += [ - "--env=MYSQL_PWD", - "--", - service, "mysql", - f"--user='{os.getenv('MYSQL_USER')}'", - f"--database='{os.getenv('MYSQL_DATABASE')}'", + "--user=$MYSQL_USER", + "--password=$MYSQL_PASSWORD", + f"--database={db_name or '$MYSQL_DATABASE'}", ] - if query: - command += [f"--execute='{query}'"] - elif "docker-compose.postgres.yml" in context.nautobot_chatops.compose_files: + elif _is_compose_included(context, "postgres"): command += [ - "--", - service, "psql", - f"--username='{os.getenv('POSTGRES_USER')}'", - f"--dbname='{os.getenv('POSTGRES_DB')}'", + "--username=$POSTGRES_USER", + f"--dbname={db_name or '$POSTGRES_DB'}", ] - if query: - command += [f"--command='{query}'"] else: raise ValueError("Unsupported database backend.") - if input: - command += [f"< '{input}'"] - if output: - command += [f"> '{output}'"] + 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_vars, pty=not (input or output or query)) + docker_compose(context, " ".join(command), env=env, pty=not (input_file or output_file or query)) @task( help={ - "input": "SQL dump file to replace the existing database with. This can be generated using `invoke backup-db` (default: `dump.sql`).", + "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, input="dump.sql"): +def import_db(context, input_file="dump.sql"): """Stop Nautobot containers and replace the current database with the dump into the running `db` container.""" docker_compose(context, "stop -- nautobot worker") - _load_dotenv() - - service = "db" - env_vars = {} - command = ["exec"] + command = ["exec -- db sh -c '"] - if "docker-compose.mysql.yml" in context.nautobot_chatops.compose_files: - env_vars["MYSQL_PWD"] = os.getenv("MYSQL_PASSWORD") + if _is_compose_included(context, "mysql"): command += [ - "--env=MYSQL_PWD", - "--", - service, "mysql", - f"--user='{os.getenv('MYSQL_USER')}'", - f"--database='{os.getenv('MYSQL_DATABASE')}'", + "--database=$MYSQL_DATABASE", + "--user=$MYSQL_USER", + "--password=$MYSQL_PASSWORD", ] - elif "docker-compose.postgres.yml" in context.nautobot_chatops.compose_files: + elif _is_compose_included(context, "postgres"): command += [ - "--", - service, "psql", - f"--username='{os.getenv('POSTGRES_USER')}'", + "--username=$POSTGRES_USER", "postgres", ] else: raise ValueError("Unsupported database backend.") - command += [f"< '{input}'"] + command += [ + "'", + f"< '{input_file}'", + ] - docker_compose(context, " ".join(command), env=env_vars, pty=False) + docker_compose(context, " ".join(command), pty=False) print("Database import complete, you can start Nautobot now: `invoke start`") @task( help={ - "output": "Ouput file, overwrite if exists (default: `dump.sql`)", + "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, output="dump.sql", readable=True): - """Dump database into `output` file from running `db` container.""" - _load_dotenv() - - service = "db" - env_vars = {} - command = ["exec"] +def backup_db(context, db_name="", output_file="dump.sql", readable=True): + """Dump database into `output_file` file from running `db` container.""" + command = ["exec -- db sh -c '"] - if "docker-compose.mysql.yml" in context.nautobot_chatops.compose_files: - env_vars["MYSQL_PWD"] = os.getenv("MYSQL_ROOT_PASSWORD") + if _is_compose_included(context, "mysql"): command += [ - "--env=MYSQL_PWD", - "--", - service, "mysqldump", "--user=root", + "--password=$MYSQL_ROOT_PASSWORD", "--add-drop-database", "--skip-extended-insert" if readable else "", "--databases", - os.getenv("MYSQL_DATABASE", ""), + db_name if db_name else "$MYSQL_DATABASE", ] - elif "docker-compose.postgres.yml" in context.nautobot_chatops.compose_files: + elif _is_compose_included(context, "postgres"): command += [ - "--", - service, "pg_dump", "--clean", "--create", "--if-exists", - f"--username='{os.getenv('POSTGRES_USER')}'", - f"--dbname='{os.getenv('POSTGRES_DB')}'", + "--username=$POSTGRES_USER", + f"--dbname={db_name or '$POSTGRES_DB'}", + "--inserts" if readable else "", ] - - if readable: - command += ["--inserts"] else: raise ValueError("Unsupported database backend.") - if output: - command += [f"> '{output}'"] + command += [ + "'", + f"> '{output_file}'", + ] - docker_compose(context, " ".join(command), env=env_vars, pty=False) + docker_compose(context, " ".join(command), pty=False) print(50 * "=") - print("The database backup has been successfully completed and saved to the file:") - print(output) - print("If you want to import this database backup, please execute the following command:") - print(f"invoke import-db --input '{output}'") + print("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 +# ------------------------------------------------------------------------------ +@task +def docs(context): + """Build and serve docs locally for development.""" + command = "mkdocs serve -v" + + if is_truthy(context.nautobot_chatops.local): + print(">>> Serving Documentation at http://localhost:8001") + run_command(context, command) + else: + 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.""" @@ -571,7 +589,7 @@ def pylint(context): def pydocstyle(context): """Run pydocstyle to validate docstring formatting adheres to NTC defined standards.""" # We exclude the /migrations/ directory since it is autogenerated code - command = 'pydocstyle --config=.pydocstyle.ini --match-dir="^(?!migrations).*"' + command = "pydocstyle ." run_command(context, command) @@ -584,7 +602,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 @@ -596,15 +614,8 @@ 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" - - run_command(context, command) + command = "nautobot-server makemigrations --dry-run --check" - -@task() -def build_and_check_docs(context): - """Build docs for use within Nautobot.""" - command = "mkdocs build --no-directory-urls --strict" run_command(context, command) @@ -615,9 +626,18 @@ def build_and_check_docs(context): "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_chatops", failfast=False, buffer=True, pattern=""): +def unittest( + context, + keepdb=False, + label="nautobot_chatops", + failfast=False, + buffer=True, + pattern="", + verbose=False, +): """Run Nautobot unit tests.""" command = f"coverage run --module nautobot.core.cli test {label}" @@ -629,6 +649,9 @@ def unittest(context, keepdb=False, label="nautobot_chatops", failfast=False, bu command += " --buffer" if pattern: command += f" -k='{pattern}'" + if verbose: + command += " --verbosity 2" + run_command(context, command) @@ -642,10 +665,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_chatops.local): @@ -662,14 +687,19 @@ 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("Building and checking docs...") + print("Running mkdocs...") build_and_check_docs(context) - print("Running unit tests...") - unittest(context, failfast=failfast) + 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) # ------------------------------------------------------------------------------